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 * 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) * 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) * 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 ## [0.15.0] - 2021-07-19
* Kill children properly (https://github.com/zellij-org/zellij/pull/601) * 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 colored_elements = color_elements(self.mode_info.palette);
let superkey = superkey(colored_elements, separator); 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 first_line = format!("{}{}", superkey, ctrl_keys);
let second_line = keybinds(&self.mode_info, cols); let second_line = keybinds(&self.mode_info, cols);

View File

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

View File

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

View File

@ -221,26 +221,26 @@ pub fn scrolling_inside_a_pane() {
let mut step_is_complete = false; let mut step_is_complete = false;
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
// cursor is in the newly opened second pane // cursor is in the newly opened second pane
remote_terminal.send_key(&format!("{:0<57}", "line1 ").as_bytes()); remote_terminal.send_key(&format!("{:0<56}", "line1 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line2 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line2 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line3 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line3 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line4 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line4 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line5 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line5 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line6 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line6 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line7 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line7 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line8 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line8 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line9 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line9 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line10 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line10 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line11 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line11 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line12 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line12 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line13 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line13 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line14 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line14 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line15 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line15 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line16 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line16 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line17 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line17 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line18 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line18 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line19 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line19 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line20 ").as_bytes()); remote_terminal.send_key(&format!("{:0<57}", "line20 ").as_bytes());
step_is_complete = true; step_is_complete = true;
} }
step_is_complete step_is_complete
@ -250,7 +250,7 @@ pub fn scrolling_inside_a_pane() {
name: "Scroll up inside pane", name: "Scroll up inside pane",
instruction: |mut remote_terminal: RemoteTerminal| -> bool { instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false; 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 // all lines have been written to the pane
remote_terminal.send_key(&SCROLL_MODE); remote_terminal.send_key(&SCROLL_MODE);
remote_terminal.send_key(&SCROLL_UP_IN_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", name: "Wait for scroll to finish",
instruction: |remote_terminal: RemoteTerminal| -> bool { instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false; 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 ") && remote_terminal.snapshot_contains("line1 ")
{ {
// scrolled up one line // scrolled up one line
@ -321,7 +321,7 @@ pub fn toggle_pane_fullscreen() {
name: "Wait for pane to become fullscreen", name: "Wait for pane to become fullscreen",
instruction: |remote_terminal: RemoteTerminal| -> bool { instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false; 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 // cursor is in full screen pane now
step_is_complete = true; step_is_complete = true;
} }
@ -785,8 +785,8 @@ pub fn accepts_basic_layout() {
name: "Wait for app to load", name: "Wait for app to load",
instruction: |remote_terminal: RemoteTerminal| -> bool { instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false; let mut step_is_complete = false;
if remote_terminal.cursor_position_is(2, 0) if remote_terminal.cursor_position_is(3, 1)
&& remote_terminal.snapshot_contains("$ █ │$") && remote_terminal.snapshot_contains("$ █ │$")
&& remote_terminal.snapshot_contains("$ ") { && remote_terminal.snapshot_contains("$ ") {
step_is_complete = true; step_is_complete = true;
} }
@ -839,7 +839,7 @@ fn focus_pane_with_mouse() {
name: "Wait for left pane to be focused", name: "Wait for left pane to be focused",
instruction: |remote_terminal: RemoteTerminal| -> bool { instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false; 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 // cursor is in the newly opened second pane
step_is_complete = true; step_is_complete = true;
} }
@ -884,26 +884,26 @@ pub fn scrolling_inside_a_pane_with_mouse() {
let mut step_is_complete = false; let mut step_is_complete = false;
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
// cursor is in the newly opened second pane // cursor is in the newly opened second pane
remote_terminal.send_key(&format!("{:0<57}", "line1 ").as_bytes()); remote_terminal.send_key(&format!("{:0<56}", "line1 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line2 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line2 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line3 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line3 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line4 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line4 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line5 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line5 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line6 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line6 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line7 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line7 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line8 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line8 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line9 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line9 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line10 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line10 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line11 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line11 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line12 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line12 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line13 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line13 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line14 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line14 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line15 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line15 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line16 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line16 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line17 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line17 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line18 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line18 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line19 ").as_bytes()); remote_terminal.send_key(&format!("{:0<58}", "line19 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line20 ").as_bytes()); remote_terminal.send_key(&format!("{:0<57}", "line20 ").as_bytes());
step_is_complete = true; step_is_complete = true;
} }
step_is_complete step_is_complete
@ -913,7 +913,7 @@ pub fn scrolling_inside_a_pane_with_mouse() {
name: "Scroll up inside pane", name: "Scroll up inside pane",
instruction: |mut remote_terminal: RemoteTerminal| -> bool { instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false; 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 // all lines have been written to the pane
remote_terminal.send_key(&normal_mouse_report(Position::new(2, 64), 64)); remote_terminal.send_key(&normal_mouse_report(Position::new(2, 64), 64));
step_is_complete = true; step_is_complete = true;
@ -925,7 +925,7 @@ pub fn scrolling_inside_a_pane_with_mouse() {
name: "Wait for scroll to finish", name: "Wait for scroll to finish",
instruction: |remote_terminal: RemoteTerminal| -> bool { instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false; 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 ") && remote_terminal.snapshot_contains("line1 ")
{ {
// scrolled up one line // scrolled up one line
@ -937,3 +937,45 @@ pub fn scrolling_inside_a_pane_with_mouse() {
.run_all_steps(); .run_all_steps();
assert_snapshot!(last_snapshot); 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(); 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( fn start_zellij_with_layout(
channel: &mut ssh2::Channel, channel: &mut ssh2::Channel,
layout_path: &str, layout_path: &str,
@ -136,7 +143,8 @@ impl<'a> RemoteTerminal<'a> {
self.current_snapshot.contains("Tip:") self.current_snapshot.contains("Tip:")
} }
pub fn status_bar_appears(&self) -> bool { 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 // 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 { pub fn snapshot_contains(&self, text: &str) -> bool {
@ -198,6 +206,7 @@ pub struct RemoteRunner {
retries_left: usize, retries_left: usize,
win_size: PositionAndSize, win_size: PositionAndSize,
layout_file_name: Option<&'static str>, layout_file_name: Option<&'static str>,
without_frames: bool,
} }
impl RemoteRunner { impl RemoteRunner {
@ -209,7 +218,7 @@ impl RemoteRunner {
let sess = ssh_connect(); let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap(); let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new(); 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); setup_remote_environment(&mut channel, win_size);
start_zellij(&mut channel, session_name.as_ref()); start_zellij(&mut channel, session_name.as_ref());
RemoteRunner { RemoteRunner {
@ -224,6 +233,33 @@ impl RemoteRunner {
retries_left: 3, retries_left: 3,
win_size, win_size,
layout_file_name: None, 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( pub fn new_with_layout(
@ -232,12 +268,11 @@ impl RemoteRunner {
layout_file_name: &'static str, layout_file_name: &'static str,
session_name: Option<String>, session_name: Option<String>,
) -> Self { ) -> Self {
// let layout_file_name = local_layout_path.file_name().unwrap(); let remote_path = Path::new(ZELLIJ_LAYOUT_PATH).join(layout_file_name);
let remote_path = Path::new(ZELLIJ_LAYOUT_PATH).join(layout_file_name); // TODO: not hardcoded
let sess = ssh_connect(); let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap(); let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new(); 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); setup_remote_environment(&mut channel, win_size);
start_zellij_with_layout( start_zellij_with_layout(
&mut channel, &mut channel,
@ -256,6 +291,7 @@ impl RemoteRunner {
retries_left: 3, retries_left: 3,
win_size, win_size,
layout_file_name: Some(layout_file_name), layout_file_name: Some(layout_file_name),
without_frames: false,
} }
} }
pub fn add_step(mut self, step: Step) -> Self { pub fn add_step(mut self, step: Step) -> Self {
@ -315,6 +351,13 @@ impl RemoteRunner {
new_runner.replace_steps(self.steps.clone()); new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner)); drop(std::mem::replace(self, new_runner));
self.run_all_steps() 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 { } 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, session_name);
new_runner.retries_left = self.retries_left - 1; new_runner.retries_left = self.retries_left - 1;

View File

@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot 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 expression: last_snapshot
--- ---
────────
$ Hi!█ $ Hi!█

View File

@ -1,10 +1,10 @@
--- ---
source: src/tests/integration/e2e.rs source: src/tests/e2e/cases.rs
expression: last_snapshot expression: last_snapshot
--- ---
Zellij  Tab #1  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 expression: last_snapshot
--- ---
Zellij  Tab #1  Zellij  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
$ │$ I am some text█ $ │$ I am some text█
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT  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. Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.

View File

@ -4,26 +4,26 @@ expression: last_snapshot
--- ---
Zellij  Tab #1  Zellij  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
$ █ │$ $ █ │$
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT  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. 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 expression: last_snapshot
--- ---
Zellij  Tab #1  Zellij  Tab #1 
─ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ nabc█ $ nabc█

View File

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

View File

@ -4,26 +4,26 @@ expression: last_snapshot
--- ---
Zellij  Tab #1  Zellij  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 1/1 ┐
$ │$ line1 000000000000000000000000000000000000000000000000000 $ │$ line1 00000000000000000000000000000000000000000000000000
│line2 00000000000000000000000000000000000000000000000000000 line2 0000000000000000000000000000000000000000000000000000
│line3 00000000000000000000000000000000000000000000000000000 line3 0000000000000000000000000000000000000000000000000000
│line4 00000000000000000000000000000000000000000000000000000 line4 0000000000000000000000000000000000000000000000000000
│line5 00000000000000000000000000000000000000000000000000000 line5 0000000000000000000000000000000000000000000000000000
│line6 00000000000000000000000000000000000000000000000000000 line6 0000000000000000000000000000000000000000000000000000
│line7 00000000000000000000000000000000000000000000000000000 line7 0000000000000000000000000000000000000000000000000000
│line8 00000000000000000000000000000000000000000000000000000 line8 0000000000000000000000000000000000000000000000000000
│line9 00000000000000000000000000000000000000000000000000000 line9 0000000000000000000000000000000000000000000000000000
│line10 0000000000000000000000000000000000000000000000000000 line10 000000000000000000000000000000000000000000000000000
│line11 0000000000000000000000000000000000000000000000000000 line11 000000000000000000000000000000000000000000000000000
│line12 0000000000000000000000000000000000000000000000000000 line12 000000000000000000000000000000000000000000000000000
│line13 0000000000000000000000000000000000000000000000000000 line13 000000000000000000000000000000000000000000000000000
│line14 0000000000000000000000000000000000000000000000000000 line14 000000000000000000000000000000000000000000000000000
│line15 0000000000000000000000000000000000000000000000000000 line15 000000000000000000000000000000000000000000000000000
│line16 0000000000000000000000000000000000000000000000000000 line16 000000000000000000000000000000000000000000000000000
│line17 0000000000000000000000000000000000000000000000000000 line17 000000000000000000000000000000000000000000000000000
│line18 0000000000000000000000000000000000000000000000000000 line18 000000000000000000000000000000000000000000000000000
│line19 000000000000000000000000000000000000000000000000000 line19 00000000000000000000000000000000000000000000000000█
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT  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. 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 expression: last_snapshot
--- ---
Zellij  Tab #1  Zellij  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
$ │$ █ $ │$ █
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT  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. 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 expression: last_snapshot
--- ---
Zellij  Tab #1  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 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}; use zellij_utils::{position::Position, vte, zellij_tile};
const TABSTOP_WIDTH: usize = 8; // TODO: is this always right? const TABSTOP_WIDTH: usize = 8; // TODO: is this always right?
const SCROLL_BACK: usize = 10_000; pub const SCROLL_BACK: usize = 10_000;
pub const MAX_TITLE_STACK_SIZE: usize = 1000;
use vte::{Params, Perform}; use vte::{Params, Perform};
use zellij_tile::data::{Palette, PaletteColor}; use zellij_tile::data::{Palette, PaletteColor};
@ -308,6 +309,7 @@ pub struct Grid {
preceding_char: Option<TerminalCharacter>, preceding_char: Option<TerminalCharacter>,
colors: Palette, colors: Palette,
output_buffer: OutputBuffer, output_buffer: OutputBuffer,
title_stack: Vec<String>,
pub should_render: bool, 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 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 pub erasure_mode: bool, // ERM
@ -318,6 +320,7 @@ pub struct Grid {
pub height: usize, pub height: usize,
pub pending_messages_to_pty: Vec<Vec<u8>>, pub pending_messages_to_pty: Vec<Vec<u8>>,
pub selection: Selection, pub selection: Selection,
pub title: Option<String>,
} }
impl Debug for Grid { impl Debug for Grid {
@ -358,6 +361,8 @@ impl Grid {
colors, colors,
output_buffer: Default::default(), output_buffer: Default::default(),
selection: Default::default(), selection: Default::default(),
title_stack: vec![],
title: None,
} }
} }
pub fn render_full_viewport(&mut self) { pub fn render_full_viewport(&mut self) {
@ -404,6 +409,23 @@ impl Grid {
pub fn cursor_shape(&self) -> CursorShape { pub fn cursor_shape(&self) -> CursorShape {
self.cursor.get_shape() 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) { fn set_horizontal_tabstop(&mut self) {
self.horizontal_tabstops.insert(self.cursor.x); self.horizontal_tabstops.insert(self.cursor.x);
} }
@ -475,8 +497,17 @@ impl Grid {
if !self.lines_above.is_empty() && self.viewport.len() == self.height { if !self.lines_above.is_empty() && self.viewport.len() == self.height {
let line_to_push_down = self.viewport.pop().unwrap(); let line_to_push_down = self.viewport.pop().unwrap();
self.lines_below.insert(0, line_to_push_down); 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.selection.move_down(1);
} }
self.output_buffer.update_all_lines(); self.output_buffer.update_all_lines();
@ -497,6 +528,24 @@ impl Grid {
self.output_buffer.update_all_lines(); 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) { pub fn change_size(&mut self, new_rows: usize, new_columns: usize) {
self.selection.reset(); self.selection.reset();
if new_columns != self.width { if new_columns != self.width {
@ -1200,7 +1249,7 @@ impl Grid {
let empty_row = Row::from_columns(vec![EMPTY_TERMINAL_CHARACTER; self.width]); 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 // 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(); let offset_from_end = l.abs();
&self.lines_above[self &self.lines_above[self
.lines_above .lines_above
@ -1211,8 +1260,12 @@ impl Grid {
} else if (l as usize) < self.height { } else if (l as usize) < self.height {
// index is in viewport but there is no line // index is in viewport but there is no line
&empty_row &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()] &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(); let excess_width = row.excess_width();
@ -1242,6 +1295,22 @@ impl Grid {
self.output_buffer.update_line(l as usize); 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 { impl Perform for Grid {
@ -1311,7 +1380,7 @@ impl Perform for Grid {
// Set window title. // Set window title.
b"0" | b"2" => { b"0" | b"2" => {
if params.len() >= 2 { if params.len() >= 2 {
let _title = params[1..] let title = params[1..]
.iter() .iter()
.flat_map(|x| str::from_utf8(x)) .flat_map(|x| str::from_utf8(x))
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
@ -1319,6 +1388,7 @@ impl Perform for Grid {
.trim() .trim()
.to_owned(); .to_owned();
// TBD: do something with title? // 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.alternative_lines_above_viewport_and_cursor = None;
self.clear_viewport_before_rendering = true; 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(); self.mark_for_rerender();
} }
Some(25) => { Some(25) => {
@ -1758,10 +1828,10 @@ impl Perform for Grid {
.push(text_area_report.as_bytes().to_vec()); .push(text_area_report.as_bytes().to_vec());
} }
22 => { 22 => {
// TODO: push title self.push_current_title_to_stack();
} }
23 => { 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::time::Instant;
use std::unimplemented; use std::unimplemented;
use crate::panes::PaneId; use crate::panes::{PaneDecoration, PaneId};
use crate::pty::VteBytes; use crate::pty::VteBytes;
use crate::tab::Pane; use crate::tab::Pane;
use crate::ui::pane_boundaries_frame::PaneBoundariesFrame;
use crate::wasm_vm::PluginInstruction; 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}; use zellij_utils::{channels::SenderWithContext, pane_size::PositionAndSize};
pub(crate) struct PluginPane { pub(crate) struct PluginPane {
@ -15,8 +18,11 @@ pub(crate) struct PluginPane {
pub invisible_borders: bool, pub invisible_borders: bool,
pub position_and_size: PositionAndSize, pub position_and_size: PositionAndSize,
pub position_and_size_override: Option<PositionAndSize>, pub position_and_size_override: Option<PositionAndSize>,
pub content_position_and_size: PositionAndSize,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>, pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub active_at: Instant, pub active_at: Instant,
pub pane_title: String,
pane_decoration: PaneDecoration,
} }
impl PluginPane { impl PluginPane {
@ -24,6 +30,7 @@ impl PluginPane {
pid: u32, pid: u32,
position_and_size: PositionAndSize, position_and_size: PositionAndSize,
send_plugin_instructions: SenderWithContext<PluginInstruction>, send_plugin_instructions: SenderWithContext<PluginInstruction>,
title: String,
) -> Self { ) -> Self {
Self { Self {
pid, pid,
@ -34,8 +41,48 @@ impl PluginPane {
position_and_size_override: None, position_and_size_override: None,
send_plugin_instructions, send_plugin_instructions,
active_at: Instant::now(), 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 { impl Pane for PluginPane {
@ -62,13 +109,20 @@ impl Pane for PluginPane {
.unwrap_or(self.position_and_size) .unwrap_or(self.position_and_size)
.cols .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) { fn reset_size_and_position_override(&mut self) {
self.position_and_size_override = None; self.position_and_size_override = None;
self.redistribute_space();
self.should_render = true; self.should_render = true;
} }
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) { fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size = *position_and_size; 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` // 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) { fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
@ -80,7 +134,7 @@ impl Pane for PluginPane {
..Default::default() ..Default::default()
}; };
self.position_and_size_override = Some(position_and_size_override); 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) { fn handle_pty_bytes(&mut self, _event: VteBytes) {
unimplemented!() unimplemented!()
@ -103,6 +157,11 @@ impl Pane for PluginPane {
fn set_should_render(&mut self, should_render: bool) { fn set_should_render(&mut self, should_render: bool) {
self.should_render = should_render; 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 { fn selectable(&self) -> bool {
self.selectable 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 // 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) // 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 // 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(); let (buf_tx, buf_rx) = channel();
self.send_plugin_instructions self.send_plugin_instructions
.send(PluginInstruction::Render( .send(PluginInstruction::Render(
buf_tx, buf_tx,
self.pid, self.pid,
self.rows(), self.get_content_rows(),
self.columns(), self.get_content_columns(),
)) ))
.unwrap(); .unwrap();
self.should_render = false; 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 { } else {
None None
} }
@ -150,50 +260,66 @@ impl Pane for PluginPane {
fn reduce_height_down(&mut self, count: usize) { fn reduce_height_down(&mut self, count: usize) {
self.position_and_size.y += count; self.position_and_size.y += count;
self.position_and_size.rows -= count; self.position_and_size.rows -= count;
self.redistribute_space();
self.should_render = true; self.should_render = true;
} }
fn increase_height_down(&mut self, count: usize) { fn increase_height_down(&mut self, count: usize) {
self.position_and_size.rows += count; self.position_and_size.rows += count;
self.redistribute_space();
self.should_render = true; self.should_render = true;
} }
fn increase_height_up(&mut self, count: usize) { fn increase_height_up(&mut self, count: usize) {
self.position_and_size.y -= count; self.position_and_size.y -= count;
self.position_and_size.rows += count; self.position_and_size.rows += count;
self.redistribute_space();
self.should_render = true; self.should_render = true;
} }
fn reduce_height_up(&mut self, count: usize) { fn reduce_height_up(&mut self, count: usize) {
self.position_and_size.rows -= count; self.position_and_size.rows -= count;
self.redistribute_space();
self.should_render = true; self.should_render = true;
} }
fn reduce_width_right(&mut self, count: usize) { fn reduce_width_right(&mut self, count: usize) {
self.position_and_size.x += count; self.position_and_size.x += count;
self.position_and_size.cols -= count; self.position_and_size.cols -= count;
self.redistribute_space();
self.should_render = true; self.should_render = true;
} }
fn reduce_width_left(&mut self, count: usize) { fn reduce_width_left(&mut self, count: usize) {
self.position_and_size.cols -= count; self.position_and_size.cols -= count;
self.redistribute_space();
self.should_render = true; self.should_render = true;
} }
fn increase_width_left(&mut self, count: usize) { fn increase_width_left(&mut self, count: usize) {
self.position_and_size.x -= count; self.position_and_size.x -= count;
self.position_and_size.cols += count; self.position_and_size.cols += count;
self.redistribute_space();
self.should_render = true; self.should_render = true;
} }
fn increase_width_right(&mut self, count: usize) { fn increase_width_right(&mut self, count: usize) {
self.position_and_size.cols += count; self.position_and_size.cols += count;
self.redistribute_space();
self.should_render = true; self.should_render = true;
} }
fn push_down(&mut self, count: usize) { fn push_down(&mut self, count: usize) {
self.position_and_size.y += count; self.position_and_size.y += count;
self.redistribute_space();
self.should_render = true;
} }
fn push_right(&mut self, count: usize) { fn push_right(&mut self, count: usize) {
self.position_and_size.x += count; self.position_and_size.x += count;
self.redistribute_space();
self.should_render = true;
} }
fn pull_left(&mut self, count: usize) { fn pull_left(&mut self, count: usize) {
self.position_and_size.x -= count; self.position_and_size.x -= count;
self.redistribute_space();
self.should_render = true;
} }
fn pull_up(&mut self, count: usize) { fn pull_up(&mut self, count: usize) {
self.position_and_size.y -= count; self.position_and_size.y -= count;
self.redistribute_space();
self.should_render = true;
} }
fn scroll_up(&mut self, _count: usize) { fn scroll_up(&mut self, _count: usize) {
//unimplemented!() //unimplemented!()
@ -231,4 +357,61 @@ impl Pane for PluginPane {
fn set_active_at(&mut self, time: Instant) { fn set_active_at(&mut self, time: Instant) {
self.active_at = time; 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::os::unix::io::RawFd;
use std::time::{self, Instant}; use std::time::{self, Instant};
use zellij_tile::data::Palette; use zellij_tile::data::Palette;
use zellij_utils::pane_size::PositionAndSize; use zellij_utils::pane_size::PositionAndSize;
use crate::panes::AnsiCode; use crate::panes::AnsiCode;
@ -20,22 +21,32 @@ use crate::tab::Pane;
pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10; pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10;
use crate::ui::pane_boundaries_frame::PaneBoundariesFrame;
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)] #[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
pub enum PaneId { pub enum PaneId {
Terminal(RawFd), Terminal(RawFd),
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct? 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 struct TerminalPane {
pub grid: Grid, pub grid: Grid,
pub pid: RawFd, pub pid: RawFd,
pub selectable: bool, pub selectable: bool,
pub position_and_size: PositionAndSize, position_and_size: PositionAndSize,
pub position_and_size_override: Option<PositionAndSize>, position_and_size_override: Option<PositionAndSize>,
pub active_at: Instant, pub active_at: Instant,
pub colors: Palette, pub colors: Palette,
vte_parser: vte::Parser, vte_parser: vte::Parser,
selection_scrolled_at: time::Instant, selection_scrolled_at: time::Instant,
content_position_and_size: PositionAndSize,
pane_title: String,
pane_decoration: PaneDecoration,
} }
impl Pane for TerminalPane { impl Pane for TerminalPane {
@ -51,24 +62,29 @@ impl Pane for TerminalPane {
fn columns(&self) -> usize { fn columns(&self) -> usize {
self.get_columns() 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) { fn reset_size_and_position_override(&mut self) {
self.position_and_size_override = None; self.position_and_size_override = None;
self.reflow_lines(); self.redistribute_space();
} }
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) { fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size = *position_and_size; 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) { 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, x,
y, y,
rows: size.rows, rows: size.rows,
cols: size.cols, cols: size.cols,
..Default::default() ..Default::default()
}; });
self.position_and_size_override = Some(position_and_size_override); self.redistribute_space();
self.reflow_lines();
} }
fn handle_pty_bytes(&mut self, bytes: VteBytes) { fn handle_pty_bytes(&mut self, bytes: VteBytes) {
for byte in bytes.iter() { for byte in bytes.iter() {
@ -78,7 +94,17 @@ impl Pane for TerminalPane {
} }
fn cursor_coordinates(&self) -> Option<(usize, usize)> { fn cursor_coordinates(&self) -> Option<(usize, usize)> {
// (x, y) // (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> { 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 // 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) { fn set_should_render(&mut self, should_render: bool) {
self.grid.should_render = should_render; 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) { 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(); self.grid.render_full_viewport();
} }
fn selectable(&self) -> bool { fn selectable(&self) -> bool {
@ -173,10 +206,10 @@ impl Pane for TerminalPane {
} }
self.grid.clear_viewport_before_rendering = false; 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() { for character_chunk in self.grid.read_changes() {
let pane_x = self.get_x(); let pane_x = self.get_content_x();
let pane_y = self.get_y(); let pane_y = self.get_content_y();
let chunk_absolute_x = pane_x + character_chunk.x; let chunk_absolute_x = pane_x + character_chunk.x;
let chunk_absolute_y = pane_y + character_chunk.y; let chunk_absolute_y = pane_y + character_chunk.y;
let terminal_characters = character_chunk.terminal_characters; let terminal_characters = character_chunk.terminal_characters;
@ -212,6 +245,13 @@ impl Pane for TerminalPane {
} }
character_styles.clear(); 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); self.set_should_render(false);
Some(vte_output) Some(vte_output)
} else { } else {
@ -224,50 +264,54 @@ impl Pane for TerminalPane {
fn reduce_height_down(&mut self, count: usize) { fn reduce_height_down(&mut self, count: usize) {
self.position_and_size.y += count; self.position_and_size.y += count;
self.position_and_size.rows -= count; self.position_and_size.rows -= count;
self.reflow_lines(); self.redistribute_space();
} }
fn increase_height_down(&mut self, count: usize) { fn increase_height_down(&mut self, count: usize) {
self.position_and_size.rows += count; self.position_and_size.rows += count;
self.reflow_lines(); self.redistribute_space();
} }
fn increase_height_up(&mut self, count: usize) { fn increase_height_up(&mut self, count: usize) {
self.position_and_size.y -= count; self.position_and_size.y -= count;
self.position_and_size.rows += count; self.position_and_size.rows += count;
self.reflow_lines(); self.redistribute_space();
} }
fn reduce_height_up(&mut self, count: usize) { fn reduce_height_up(&mut self, count: usize) {
self.position_and_size.rows -= count; self.position_and_size.rows -= count;
self.reflow_lines(); self.redistribute_space();
} }
fn reduce_width_right(&mut self, count: usize) { fn reduce_width_right(&mut self, count: usize) {
self.position_and_size.x += count; self.position_and_size.x += count;
self.position_and_size.cols -= count; self.position_and_size.cols -= count;
self.reflow_lines(); self.redistribute_space();
} }
fn reduce_width_left(&mut self, count: usize) { fn reduce_width_left(&mut self, count: usize) {
self.position_and_size.cols -= count; self.position_and_size.cols -= count;
self.reflow_lines(); self.redistribute_space();
} }
fn increase_width_left(&mut self, count: usize) { fn increase_width_left(&mut self, count: usize) {
self.position_and_size.x -= count; self.position_and_size.x -= count;
self.position_and_size.cols += count; self.position_and_size.cols += count;
self.reflow_lines(); self.redistribute_space();
} }
fn increase_width_right(&mut self, count: usize) { fn increase_width_right(&mut self, count: usize) {
self.position_and_size.cols += count; self.position_and_size.cols += count;
self.reflow_lines(); self.redistribute_space();
} }
fn push_down(&mut self, count: usize) { fn push_down(&mut self, count: usize) {
self.position_and_size.y += count; self.position_and_size.y += count;
self.redistribute_space();
} }
fn push_right(&mut self, count: usize) { fn push_right(&mut self, count: usize) {
self.position_and_size.x += count; self.position_and_size.x += count;
self.redistribute_space();
} }
fn pull_left(&mut self, count: usize) { fn pull_left(&mut self, count: usize) {
self.position_and_size.x -= count; self.position_and_size.x -= count;
self.redistribute_space();
} }
fn pull_up(&mut self, count: usize) { fn pull_up(&mut self, count: usize) {
self.position_and_size.y -= count; self.position_and_size.y -= count;
self.redistribute_space();
} }
fn scroll_up(&mut self, count: usize) { fn scroll_up(&mut self, count: usize) {
self.grid.move_viewport_up(count); self.grid.move_viewport_up(count);
@ -337,12 +381,69 @@ impl Pane for TerminalPane {
fn get_selected_text(&self) -> Option<String> { fn get_selected_text(&self) -> Option<String> {
self.grid.get_selected_text() 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 { 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); let grid = Grid::new(position_and_size.rows, position_and_size.cols, palette);
TerminalPane { TerminalPane {
pane_decoration: PaneDecoration::ContentOffset((0, 0)),
content_position_and_size: position_and_size,
pid, pid,
grid, grid,
selectable: true, selectable: true,
@ -352,35 +453,55 @@ impl TerminalPane {
active_at: Instant::now(), active_at: Instant::now(),
colors: palette, colors: palette,
selection_scrolled_at: time::Instant::now(), selection_scrolled_at: time::Instant::now(),
pane_title: initial_pane_title,
} }
} }
pub fn get_x(&self) -> usize { 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, 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 { 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, 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 { 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, 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 { 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, 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) { fn reflow_lines(&mut self) {
let rows = self.get_rows(); let rows = self.get_content_rows();
let columns = self.get_columns(); let columns = self.get_content_columns();
self.grid.change_size(rows, columns); self.grid.change_size(rows, columns);
self.set_should_render(true); self.set_should_render(true);
} }
@ -391,6 +512,24 @@ impl TerminalPane {
// (x, y) // (x, y)
self.grid.cursor_coordinates() 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)] #[cfg(test)]

View File

@ -608,9 +608,9 @@ fn copy_selected_text_from_lines_below() {
grid.move_viewport_up(40); 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 // 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(); let text = grid.get_selected_text();
assert_eq!( assert_eq!(
text.unwrap(), text.unwrap(),

View File

@ -15,7 +15,7 @@ pub fn scrolling_inside_a_pane() {
}; };
let pid = 1; let pid = 1;
let palette = Palette::default(); 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(); let mut text_to_fill_pane = String::new();
for i in 0..30 { for i in 0..30 {
text_to_fill_pane.push_str(&format!("\rline {}\n", i + 1)); 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) .send_to_screen(ScreenInstruction::ToggleActiveTerminalFullscreen)
.unwrap(); .unwrap();
} }
Action::TogglePaneFrames => {
session
.senders
.send_to_screen(ScreenInstruction::TogglePaneFrames)
.unwrap();
}
Action::NewPane(direction) => { Action::NewPane(direction) => {
let shell = session.default_shell.clone(); let shell = session.default_shell.clone();
let pty_instr = match direction { let pty_instr = match direction {

View File

@ -56,6 +56,7 @@ pub(crate) enum ScreenInstruction {
ClearScroll, ClearScroll,
CloseFocusedPane, CloseFocusedPane,
ToggleActiveTerminalFullscreen, ToggleActiveTerminalFullscreen,
TogglePaneFrames,
SetSelectable(PaneId, bool, usize), SetSelectable(PaneId, bool, usize),
SetFixedHeight(PaneId, usize, usize), SetFixedHeight(PaneId, usize, usize),
SetFixedWidth(PaneId, usize, usize), SetFixedWidth(PaneId, usize, usize),
@ -112,6 +113,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::ToggleActiveTerminalFullscreen => { ScreenInstruction::ToggleActiveTerminalFullscreen => {
ScreenContext::ToggleActiveTerminalFullscreen ScreenContext::ToggleActiveTerminalFullscreen
} }
ScreenInstruction::TogglePaneFrames => ScreenContext::TogglePaneFrames,
ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable,
ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders, ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders,
ScreenInstruction::SetFixedHeight(..) => ScreenContext::SetFixedHeight, ScreenInstruction::SetFixedHeight(..) => ScreenContext::SetFixedHeight,
@ -153,6 +155,7 @@ pub(crate) struct Screen {
mode_info: ModeInfo, mode_info: ModeInfo,
colors: Palette, colors: Palette,
session_state: Arc<RwLock<SessionState>>, session_state: Arc<RwLock<SessionState>>,
draw_pane_frames: bool,
} }
impl Screen { impl Screen {
@ -163,6 +166,7 @@ impl Screen {
max_panes: Option<usize>, max_panes: Option<usize>,
mode_info: ModeInfo, mode_info: ModeInfo,
session_state: Arc<RwLock<SessionState>>, session_state: Arc<RwLock<SessionState>>,
draw_pane_frames: bool,
) -> Self { ) -> Self {
Screen { Screen {
bus, bus,
@ -173,6 +177,7 @@ impl Screen {
tabs: BTreeMap::new(), tabs: BTreeMap::new(),
mode_info, mode_info,
session_state, session_state,
draw_pane_frames,
} }
} }
@ -193,6 +198,7 @@ impl Screen {
self.mode_info.clone(), self.mode_info.clone(),
self.colors, self.colors,
self.session_state.clone(), self.session_state.clone(),
self.draw_pane_frames,
); );
self.active_tab_index = Some(tab_index); self.active_tab_index = Some(tab_index);
self.tabs.insert(tab_index, tab); self.tabs.insert(tab_index, tab);
@ -359,6 +365,7 @@ impl Screen {
self.mode_info.clone(), self.mode_info.clone(),
self.colors, self.colors,
self.session_state.clone(), self.session_state.clone(),
self.draw_pane_frames,
); );
tab.apply_layout(layout, new_pids, tab_index); tab.apply_layout(layout, new_pids, tab_index);
self.active_tab_index = Some(tab_index); self.active_tab_index = Some(tab_index);
@ -405,6 +412,7 @@ impl Screen {
self.mode_info = mode_info; self.mode_info = mode_info;
for tab in self.tabs.values_mut() { for tab in self.tabs.values_mut() {
tab.mode_info = self.mode_info.clone(); tab.mode_info = self.mode_info.clone();
tab.mark_active_pane_for_rerender();
} }
} }
pub fn move_focus_left_or_previous_tab(&mut self) { 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>>, session_state: Arc<RwLock<SessionState>>,
) { ) {
let capabilities = config_options.simplified_ui; let capabilities = config_options.simplified_ui;
let draw_pane_frames = !config_options.no_pane_frames;
let mut screen = Screen::new( let mut screen = Screen::new(
bus, bus,
@ -443,6 +452,7 @@ pub(crate) fn screen_thread_main(
}, },
), ),
session_state, session_state,
draw_pane_frames,
); );
loop { loop {
let (event, mut err_ctx) = screen let (event, mut err_ctx) = screen
@ -663,6 +673,13 @@ pub(crate) fn screen_thread_main(
.unwrap() .unwrap()
.toggle_active_pane_fullscreen(); .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) => { ScreenInstruction::NewTab(pane_id) => {
screen.new_tab(pane_id); screen.new_tab(pane_id);
screen 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 zellij_utils::zellij_tile;
use crate::tab::Pane; 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 { pub struct Boundaries {
columns: usize, position_and_size: PositionAndSize,
rows: usize,
// boundary_characters: HashMap<Coordinates, BoundaryType>,
boundary_characters: HashMap<Coordinates, BoundarySymbol>, boundary_characters: HashMap<Coordinates, BoundarySymbol>,
} }
impl Boundaries { impl Boundaries {
pub fn new(columns: u16, rows: u16) -> Self { pub fn new(position_and_size: &PositionAndSize) -> Self {
let columns = columns as usize;
let rows = rows as usize;
Boundaries { Boundaries {
columns, position_and_size: *position_and_size,
rows,
boundary_characters: HashMap::new(), boundary_characters: HashMap::new(),
} }
} }
pub fn add_rect(&mut self, rect: &dyn Pane, input_mode: InputMode, palette: Option<Palette>) { 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() { let color = match palette.is_some() {
true => match input_mode { true => match input_mode {
InputMode::Normal | InputMode::Locked => Some(palette.unwrap().green), InputMode::Normal | InputMode::Locked => Some(palette.unwrap().green),
@ -479,15 +429,19 @@ impl Boundaries {
}, },
false => None, false => None,
}; };
if rect.x() > 0 { if rect.x() > self.position_and_size.x {
// left boundary
let boundary_x_coords = rect.x() - 1; let boundary_x_coords = rect.x() - 1;
let first_row_coordinates = self.rect_right_boundary_row_start(rect); let first_row_coordinates = self.rect_right_boundary_row_start(rect);
let last_row_coordinates = self.rect_right_boundary_row_end(rect); let last_row_coordinates = self.rect_right_boundary_row_end(rect);
for row in first_row_coordinates..last_row_coordinates { for row in first_row_coordinates..last_row_coordinates {
let coordinates = Coordinates::new(boundary_x_coords, row); 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) 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) BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color)
} else { } else {
BoundarySymbol::new(boundary_type::VERTICAL).color(color) BoundarySymbol::new(boundary_type::VERTICAL).color(color)
@ -503,15 +457,19 @@ impl Boundaries {
self.boundary_characters.insert(coordinates, next_symbol); 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 boundary_y_coords = rect.y() - 1;
let first_col_coordinates = self.rect_bottom_boundary_col_start(rect); let first_col_coordinates = self.rect_bottom_boundary_col_start(rect);
let last_col_coordinates = self.rect_bottom_boundary_col_end(rect); let last_col_coordinates = self.rect_bottom_boundary_col_end(rect);
for col in first_col_coordinates..last_col_coordinates { for col in first_col_coordinates..last_col_coordinates {
let coordinates = Coordinates::new(col, boundary_y_coords); 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) 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) BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color)
} else { } else {
BoundarySymbol::new(boundary_type::HORIZONTAL).color(color) BoundarySymbol::new(boundary_type::HORIZONTAL).color(color)
@ -528,15 +486,18 @@ impl Boundaries {
} }
} }
if self.rect_right_boundary_is_before_screen_edge(rect) { if self.rect_right_boundary_is_before_screen_edge(rect) {
// let boundary_x_coords = self.rect_right_boundary_x_coords(rect); // right boundary
let boundary_x_coords = rect.right_boundary_x_coords(); let boundary_x_coords = rect.right_boundary_x_coords() - 1;
let first_row_coordinates = self.rect_right_boundary_row_start(rect); let first_row_coordinates = self.rect_right_boundary_row_start(rect);
let last_row_coordinates = self.rect_right_boundary_row_end(rect); let last_row_coordinates = self.rect_right_boundary_row_end(rect);
for row in first_row_coordinates..last_row_coordinates { for row in first_row_coordinates..last_row_coordinates {
let coordinates = Coordinates::new(boundary_x_coords, row); 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) 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) BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color)
} else { } else {
BoundarySymbol::new(boundary_type::VERTICAL).color(color) BoundarySymbol::new(boundary_type::VERTICAL).color(color)
@ -553,14 +514,18 @@ impl Boundaries {
} }
} }
if self.rect_bottom_boundary_is_before_screen_edge(rect) { 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 first_col_coordinates = self.rect_bottom_boundary_col_start(rect);
let last_col_coordinates = self.rect_bottom_boundary_col_end(rect); let last_col_coordinates = self.rect_bottom_boundary_col_end(rect);
for col in first_col_coordinates..last_col_coordinates { for col in first_col_coordinates..last_col_coordinates {
let coordinates = Coordinates::new(col, boundary_y_coords); 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) 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) BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color)
} else { } else {
BoundarySymbol::new(boundary_type::HORIZONTAL).color(color) BoundarySymbol::new(boundary_type::HORIZONTAL).color(color)
@ -590,27 +555,20 @@ impl Boundaries {
vte_output vte_output
} }
fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool { 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 { 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 { fn rect_right_boundary_row_start(&self, rect: &dyn Pane) -> usize {
if rect.y() == 0 { if rect.y() > self.position_and_size.y {
0
} else {
rect.y() - 1 rect.y() - 1
} else {
self.position_and_size.y
} }
} }
fn rect_right_boundary_row_end(&self, rect: &dyn Pane) -> usize { fn rect_right_boundary_row_end(&self, rect: &dyn Pane) -> usize {
let rect_bottom_row = rect.y() + rect.rows(); rect.y() + rect.rows()
// we do this because unless we're on the screen edge, we'd like to go one extra row to
// connect to whatever boundary is beneath us
if rect_bottom_row == self.rows {
rect_bottom_row
} else {
rect_bottom_row + 1
}
} }
fn rect_bottom_boundary_col_start(&self, rect: &dyn Pane) -> usize { fn rect_bottom_boundary_col_start(&self, rect: &dyn Pane) -> usize {
if rect.x() == 0 { if rect.x() == 0 {
@ -620,13 +578,12 @@ impl Boundaries {
} }
} }
fn rect_bottom_boundary_col_end(&self, rect: &dyn Pane) -> usize { fn rect_bottom_boundary_col_end(&self, rect: &dyn Pane) -> usize {
let rect_right_col = rect.x() + rect.columns(); 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 fn is_fully_inside_screen(&self, rect: &dyn Pane) -> bool {
if rect_right_col == self.columns { rect.x() >= self.position_and_size.x
rect_right_col && rect.x() + rect.columns() <= self.position_and_size.x + self.position_and_size.cols
} else { && rect.y() >= self.position_and_size.y
rect_right_col + 1 && 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 boundaries;
pub mod pane_boundaries_frame;
pub mod pane_resizer; pub mod pane_resizer;
pub mod pane_resizer_beta; 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()) (pane.x(), pane.y(), pane.columns(), pane.rows())
}; };
let panes_to_pull = self.panes.values_mut().filter(|p| { 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
|| p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows) || 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()) (pane.x(), pane.y(), pane.columns(), pane.rows())
}; };
let panes_to_pull = self.panes.values_mut().filter(|p| { 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
|| p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns) || 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()) (pane.x(), pane.y(), pane.columns(), pane.rows())
}; };
let panes_to_push = self.panes.values_mut().filter(|p| { 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
|| p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns) || 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()) (pane.x(), pane.y(), pane.columns(), pane.rows())
}; };
let panes_to_push = self.panes.values_mut().filter(|p| { 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
|| p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows) || 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(); let pane = self.panes.get_mut(id).unwrap();
pane.reduce_height_up(count); pane.reduce_height_up(count);
if let PaneId::Terminal(pid) = id { if let PaneId::Terminal(pid) = id {
self.os_api self.os_api.set_terminal_size_using_fd(
.set_terminal_size_using_fd(*pid, pane.columns() as u16, pane.rows() as u16); *pid,
pane.get_content_columns() as u16,
pane.get_content_rows() as u16,
);
} }
} }
fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) { fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) {
let pane = self.panes.get_mut(id).unwrap(); let pane = self.panes.get_mut(id).unwrap();
pane.increase_height_down(count); pane.increase_height_down(count);
if let PaneId::Terminal(pid) = pane.pid() { if let PaneId::Terminal(pid) = pane.pid() {
self.os_api self.os_api.set_terminal_size_using_fd(
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); pid,
pane.get_content_columns() as u16,
pane.get_content_rows() as u16,
);
} }
} }
fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) { fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) {
let pane = self.panes.get_mut(id).unwrap(); let pane = self.panes.get_mut(id).unwrap();
pane.increase_width_right(count); pane.increase_width_right(count);
if let PaneId::Terminal(pid) = pane.pid() { if let PaneId::Terminal(pid) = pane.pid() {
self.os_api self.os_api.set_terminal_size_using_fd(
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); pid,
pane.get_content_columns() as u16,
pane.get_content_rows() as u16,
);
} }
} }
fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) { fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) {
let pane = self.panes.get_mut(id).unwrap(); let pane = self.panes.get_mut(id).unwrap();
pane.reduce_width_left(count); pane.reduce_width_left(count);
if let PaneId::Terminal(pid) = pane.pid() { if let PaneId::Terminal(pid) = pane.pid() {
self.os_api self.os_api.set_terminal_size_using_fd(
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); 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, increase_by: usize,
) -> Option<PaneId> { ) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter( let next_pane_candidates = panes.values().filter(
|p| { |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
p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of)
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
); );
let resizable_candidates = let resizable_candidates =
next_pane_candidates.filter(|p| p.can_increase_height_by(increase_by)); 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, increase_by: usize,
) -> Option<PaneId> { ) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter( 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 = let resizable_candidates =
next_pane_candidates.filter(|p| p.can_increase_width_by(increase_by)); 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, reduce_by: usize,
) -> Option<PaneId> { ) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter( 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)); 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 { 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, reduce_by: usize,
) -> Option<PaneId> { ) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter( let next_pane_candidates = panes.values().filter(
|p| { |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
p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of)
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
); );
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_height_by(reduce_by)); 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 { resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
@ -346,7 +354,7 @@ fn find_increasable_horizontal_chain(
{ {
Some(leftmost_pane) => { Some(leftmost_pane) => {
if !leftmost_pane.can_increase_height_by(increase_by) { 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; continue;
} }
let mut panes_to_resize = vec![]; let mut panes_to_resize = vec![];
@ -365,7 +373,7 @@ fn find_increasable_horizontal_chain(
current_pane = panes.get(&next_pane_id).unwrap(); current_pane = panes.get(&next_pane_id).unwrap();
} }
None => { None => {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
break; break;
} }
}; };
@ -396,7 +404,7 @@ fn find_increasable_vertical_chain(
{ {
Some(topmost_pane) => { Some(topmost_pane) => {
if !topmost_pane.can_increase_width_by(increase_by) { 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; continue;
} }
let mut panes_to_resize = vec![]; let mut panes_to_resize = vec![];
@ -415,7 +423,7 @@ fn find_increasable_vertical_chain(
current_pane = panes.get(&next_pane_id).unwrap(); current_pane = panes.get(&next_pane_id).unwrap();
} }
None => { None => {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
break; break;
} }
}; };
@ -446,7 +454,7 @@ fn find_reducible_horizontal_chain(
{ {
Some(leftmost_pane) => { Some(leftmost_pane) => {
if !leftmost_pane.can_reduce_height_by(reduce_by) { 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; continue;
} }
let mut panes_to_resize = vec![]; let mut panes_to_resize = vec![];
@ -465,7 +473,7 @@ fn find_reducible_horizontal_chain(
current_pane = panes.get(&next_pane_id).unwrap(); current_pane = panes.get(&next_pane_id).unwrap();
} }
None => { None => {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
break; break;
} }
}; };
@ -496,7 +504,7 @@ fn find_reducible_vertical_chain(
{ {
Some(topmost_pane) => { Some(topmost_pane) => {
if !topmost_pane.can_reduce_width_by(increase_by) { 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; continue;
} }
let mut panes_to_resize = vec![]; let mut panes_to_resize = vec![];
@ -515,7 +523,7 @@ fn find_reducible_vertical_chain(
current_pane = panes.get(&next_pane_id).unwrap(); current_pane = panes.get(&next_pane_id).unwrap();
} }
None => { None => {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
break; break;
} }
}; };

View File

View File

@ -82,7 +82,14 @@ fn create_new_screen(position_and_size: PositionAndSize) -> Screen {
let max_panes = None; let max_panes = None;
let mode_info = ModeInfo::default(); let mode_info = ModeInfo::default();
let session_state = Arc::new(RwLock::new(SessionState::Attached)); 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] #[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)); drop(bus.senders.send_to_screen(ScreenInstruction::Render));
} }
PluginInstruction::Render(buf_tx, pid, rows, cols) => { 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 (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let render = instance.exports.get_function("render").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(); buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
} }
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Exit => break, PluginInstruction::Exit => break,
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -56,6 +56,8 @@ pub enum Action {
PageScrollDown, PageScrollDown,
/// Toggle between fullscreen focus pane and normal layout. /// Toggle between fullscreen focus pane and normal layout.
ToggleFocusFullscreen, ToggleFocusFullscreen,
/// Toggle frames around panes in the UI
TogglePaneFrames,
/// Toggle between sending text commands to all panes on the current tab and normal mode. /// Toggle between sending text commands to all panes on the current tab and normal mode.
ToggleActiveSyncTab, ToggleActiveSyncTab,
/// Open a new pane in the specified direction (relative to focus). /// 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 parts: Vec<Layout>,
pub split_size: Option<SplitSize>, pub split_size: Option<SplitSize>,
pub run: Option<Run>, pub run: Option<Run>,
#[serde(default)]
pub borderless: bool,
} }
type LayoutResult = Result<Layout, ConfigError>; type LayoutResult = Result<Layout, ConfigError>;
@ -141,6 +143,14 @@ impl Layout {
total_panes 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>> { pub fn extract_run_instructions(&self) -> Vec<Option<Run>> {
let mut run_instructions = vec![]; let mut run_instructions = vec![];
if self.parts.is_empty() { if self.parts.is_empty() {
@ -168,7 +178,7 @@ fn split_space_to_parts_vertically(
let mut split_parts = Vec::new(); let mut split_parts = Vec::new();
let mut current_x_position = space_to_split.x; let mut current_x_position = space_to_split.x;
let mut current_width = 0; 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(); let mut parts_to_grow = Vec::new();
@ -192,7 +202,7 @@ fn split_space_to_parts_vertically(
..Default::default() ..Default::default()
}); });
current_width += columns; current_width += columns;
current_x_position += columns + 1; // 1 for gap current_x_position += columns;
} }
if current_width > max_width { if current_width > max_width {
@ -210,7 +220,7 @@ fn split_space_to_parts_vertically(
last_flexible_index = idx; last_flexible_index = idx;
} }
current_width += part.cols; 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 split_parts = Vec::new();
let mut current_y_position = space_to_split.y; let mut current_y_position = space_to_split.y;
let mut current_height = 0; 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(); let mut parts_to_grow = Vec::new();
@ -256,7 +266,7 @@ fn split_space_to_parts_horizontally(
..Default::default() ..Default::default()
}); });
current_height += rows; current_height += rows;
current_y_position += rows + 1; // 1 for gap current_y_position += rows;
} }
if current_height > max_height { if current_height > max_height {
@ -275,7 +285,7 @@ fn split_space_to_parts_horizontally(
last_flexible_index = idx; last_flexible_index = idx;
} }
current_height += part.rows; 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()), ("r".to_string(), "Right split".to_string()),
("x".to_string(), "Close".to_string()), ("x".to_string(), "Close".to_string()),
("f".to_string(), "Fullscreen".to_string()), ("f".to_string(), "Fullscreen".to_string()),
("z".to_string(), "Frames".to_string()),
], ],
InputMode::Tab => vec![ InputMode::Tab => vec![
("←↓↑→".to_string(), "Move focus".to_string()), ("←↓↑→".to_string(), "Move focus".to_string()),

View File

@ -58,6 +58,9 @@ pub struct Options {
#[serde(default)] #[serde(default)]
/// Disable handling of mouse events /// Disable handling of mouse events
pub disable_mouse_mode: bool, pub disable_mouse_mode: bool,
#[structopt(long)]
#[serde(default)]
pub no_pane_frames: bool,
/// Set behaviour on force close (quit or detach) /// Set behaviour on force close (quit or detach)
#[structopt(long)] #[structopt(long)]
pub on_force_close: Option<OnForceClose>, pub on_force_close: Option<OnForceClose>,
@ -80,6 +83,7 @@ impl Options {
let simplified_ui = merge_bool(other.simplified_ui, self.simplified_ui); 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 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_mode = other.default_mode.or(self.default_mode);
let default_shell = other.default_shell.or_else(|| self.default_shell.clone()); let default_shell = other.default_shell.or_else(|| self.default_shell.clone());
@ -94,6 +98,7 @@ impl Options {
default_shell, default_shell,
layout_dir, layout_dir,
disable_mouse_mode, disable_mouse_mode,
no_pane_frames,
on_force_close, 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 /// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
/// in character rows and columns. /// 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 struct PositionAndSize {
pub x: usize, pub x: usize,
pub y: usize, pub y: usize,
@ -34,4 +34,16 @@ impl PositionAndSize {
let row = point.line.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 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) 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()) from_utf8(&strip(s.as_bytes()).unwrap())
.unwrap() .unwrap()
.chars() .chars()