mirror of
https://github.com/zellij-org/zellij.git
synced 2024-11-22 22:26:54 +03:00
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:
parent
426cee728a
commit
a37d3e5889
@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
* Bound by default to `^c` in `scroll` mode, scrolls to bottom and exists the scroll mode
|
||||
* Simplify deserialization slightly (https://github.com/zellij-org/zellij/pull/633)
|
||||
* Fix update plugin attributes on inactive tab (https://github.com/zellij-org/zellij/pull/634)
|
||||
* New pane UI: draw pane frames - can be disabled with ctrl-p + z, or through configuration (https://github.com/zellij-org/zellij/pull/643)
|
||||
|
||||
## [0.15.0] - 2021-07-19
|
||||
* Kill children properly (https://github.com/zellij-org/zellij/pull/601)
|
||||
|
@ -154,7 +154,11 @@ impl ZellijPlugin for State {
|
||||
|
||||
let colored_elements = color_elements(self.mode_info.palette);
|
||||
let superkey = superkey(colored_elements, separator);
|
||||
let ctrl_keys = ctrl_keys(&self.mode_info, cols - superkey.len, separator);
|
||||
let ctrl_keys = ctrl_keys(
|
||||
&self.mode_info,
|
||||
cols.saturating_sub(superkey.len),
|
||||
separator,
|
||||
);
|
||||
|
||||
let first_line = format!("{}{}", superkey, ctrl_keys);
|
||||
let second_line = keybinds(&self.mode_info, cols);
|
||||
|
@ -47,10 +47,10 @@ impl FsEntry {
|
||||
FsEntry::Dir(_, s) => s.to_string(),
|
||||
FsEntry::File(_, s) => pb::convert(*s as f64),
|
||||
};
|
||||
let space = width - info.len();
|
||||
let space = width.saturating_sub(info.len());
|
||||
let name = self.name();
|
||||
if space - 1 < name.len() {
|
||||
[&name[..space - 2], &info].join("~ ")
|
||||
if space.saturating_sub(1) < name.len() {
|
||||
[&name[..space.saturating_sub(2)], &info].join("~ ")
|
||||
} else {
|
||||
let padding = " ".repeat(space - name.len());
|
||||
[name, padding, info].concat()
|
||||
|
@ -191,7 +191,7 @@ pub fn tab_line(
|
||||
&mut tabs_before_active,
|
||||
&mut tabs_after_active,
|
||||
&mut tabs_to_render,
|
||||
cols - prefix.len,
|
||||
cols.saturating_sub(prefix.len),
|
||||
);
|
||||
|
||||
let mut tab_line: Vec<LinePart> = vec![];
|
||||
@ -200,7 +200,7 @@ pub fn tab_line(
|
||||
&mut tabs_before_active,
|
||||
&mut tabs_to_render,
|
||||
&mut tab_line,
|
||||
cols - prefix.len,
|
||||
cols.saturating_sub(prefix.len),
|
||||
palette,
|
||||
tab_separator(capabilities),
|
||||
);
|
||||
@ -210,7 +210,7 @@ pub fn tab_line(
|
||||
add_next_tabs_msg(
|
||||
&mut tabs_after_active,
|
||||
&mut tab_line,
|
||||
cols - prefix.len,
|
||||
cols.saturating_sub(prefix.len),
|
||||
palette,
|
||||
tab_separator(capabilities),
|
||||
);
|
||||
|
@ -221,26 +221,26 @@ pub fn scrolling_inside_a_pane() {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
remote_terminal.send_key(&format!("{:0<57}", "line1 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line2 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line3 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line4 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line5 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line6 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line7 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line8 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line9 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line10 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line11 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line12 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line13 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line14 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line15 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line16 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line17 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line18 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line19 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line20 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<56}", "line1 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line2 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line3 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line4 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line5 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line6 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line7 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line8 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line9 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line10 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line11 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line12 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line13 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line14 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line15 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line16 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line17 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line18 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line19 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<57}", "line20 ").as_bytes());
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
@ -250,7 +250,7 @@ pub fn scrolling_inside_a_pane() {
|
||||
name: "Scroll up inside pane",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(119, 20) {
|
||||
if remote_terminal.cursor_position_is(118, 20) {
|
||||
// all lines have been written to the pane
|
||||
remote_terminal.send_key(&SCROLL_MODE);
|
||||
remote_terminal.send_key(&SCROLL_UP_IN_SCROLL_MODE);
|
||||
@ -263,7 +263,7 @@ pub fn scrolling_inside_a_pane() {
|
||||
name: "Wait for scroll to finish",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(119, 20)
|
||||
if remote_terminal.cursor_position_is(118, 20)
|
||||
&& remote_terminal.snapshot_contains("line1 ")
|
||||
{
|
||||
// scrolled up one line
|
||||
@ -321,7 +321,7 @@ pub fn toggle_pane_fullscreen() {
|
||||
name: "Wait for pane to become fullscreen",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(2, 0) {
|
||||
if remote_terminal.cursor_position_is(2, 2) {
|
||||
// cursor is in full screen pane now
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -785,9 +785,9 @@ pub fn accepts_basic_layout() {
|
||||
name: "Wait for app to load",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(2, 0)
|
||||
&& remote_terminal.snapshot_contains("$ █ │$")
|
||||
&& remote_terminal.snapshot_contains("$ ") {
|
||||
if remote_terminal.cursor_position_is(3, 1)
|
||||
&& remote_terminal.snapshot_contains("$ █ ││$")
|
||||
&& remote_terminal.snapshot_contains("$ ") {
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
@ -839,7 +839,7 @@ fn focus_pane_with_mouse() {
|
||||
name: "Wait for left pane to be focused",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(2, 2) && remote_terminal.tip_appears() {
|
||||
if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -884,26 +884,26 @@ pub fn scrolling_inside_a_pane_with_mouse() {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
remote_terminal.send_key(&format!("{:0<57}", "line1 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line2 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line3 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line4 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line5 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line6 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line7 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line8 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line9 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line10 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line11 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line12 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line13 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line14 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line15 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line16 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line17 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line18 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line19 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line20 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<56}", "line1 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line2 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line3 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line4 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line5 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line6 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line7 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line8 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line9 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line10 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line11 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line12 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line13 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line14 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line15 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line16 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line17 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line18 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line19 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<57}", "line20 ").as_bytes());
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
@ -913,7 +913,7 @@ pub fn scrolling_inside_a_pane_with_mouse() {
|
||||
name: "Scroll up inside pane",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(119, 20) {
|
||||
if remote_terminal.cursor_position_is(118, 20) {
|
||||
// all lines have been written to the pane
|
||||
remote_terminal.send_key(&normal_mouse_report(Position::new(2, 64), 64));
|
||||
step_is_complete = true;
|
||||
@ -925,7 +925,7 @@ pub fn scrolling_inside_a_pane_with_mouse() {
|
||||
name: "Wait for scroll to finish",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(119, 20)
|
||||
if remote_terminal.cursor_position_is(118, 20)
|
||||
&& remote_terminal.snapshot_contains("line1 ")
|
||||
{
|
||||
// scrolled up one line
|
||||
@ -937,3 +937,45 @@ pub fn scrolling_inside_a_pane_with_mouse() {
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn start_without_pane_frames() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let last_snapshot = RemoteRunner::new_without_frames("no_pane_frames", fake_win_size, None)
|
||||
.add_step(Step {
|
||||
name: "Split pane to the right",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 1)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
// back to normal mode after split
|
||||
remote_terminal.send_key(&ENTER);
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for new pane to appear",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(62, 1) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
@ -57,6 +57,13 @@ fn start_zellij(channel: &mut ssh2::Channel, session_name: Option<&String>) {
|
||||
channel.flush().unwrap();
|
||||
}
|
||||
|
||||
fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
|
||||
channel
|
||||
.write_all(format!("{} options --no-pane-frames\n", ZELLIJ_EXECUTABLE_LOCATION).as_bytes())
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
}
|
||||
|
||||
fn start_zellij_with_layout(
|
||||
channel: &mut ssh2::Channel,
|
||||
layout_path: &str,
|
||||
@ -136,7 +143,8 @@ impl<'a> RemoteTerminal<'a> {
|
||||
self.current_snapshot.contains("Tip:")
|
||||
}
|
||||
pub fn status_bar_appears(&self) -> bool {
|
||||
self.current_snapshot.contains("Ctrl +") && !self.current_snapshot.contains("─────")
|
||||
self.current_snapshot.contains("Ctrl +")
|
||||
// self.current_snapshot.contains("Ctrl +") && !self.current_snapshot.contains("─────")
|
||||
// this is a bug that happens because the app draws borders around the status bar momentarily on first render
|
||||
}
|
||||
pub fn snapshot_contains(&self, text: &str) -> bool {
|
||||
@ -198,6 +206,7 @@ pub struct RemoteRunner {
|
||||
retries_left: usize,
|
||||
win_size: PositionAndSize,
|
||||
layout_file_name: Option<&'static str>,
|
||||
without_frames: bool,
|
||||
}
|
||||
|
||||
impl RemoteRunner {
|
||||
@ -209,7 +218,7 @@ impl RemoteRunner {
|
||||
let sess = ssh_connect();
|
||||
let mut channel = sess.channel_session().unwrap();
|
||||
let vte_parser = vte::Parser::new();
|
||||
let terminal_output = TerminalPane::new(0, win_size, Palette::default());
|
||||
let terminal_output = TerminalPane::new(0, win_size, Palette::default(), 0); // 0 is the pane index
|
||||
setup_remote_environment(&mut channel, win_size);
|
||||
start_zellij(&mut channel, session_name.as_ref());
|
||||
RemoteRunner {
|
||||
@ -224,6 +233,33 @@ impl RemoteRunner {
|
||||
retries_left: 3,
|
||||
win_size,
|
||||
layout_file_name: None,
|
||||
without_frames: false,
|
||||
}
|
||||
}
|
||||
pub fn new_without_frames(
|
||||
test_name: &'static str,
|
||||
win_size: PositionAndSize,
|
||||
session_name: Option<String>,
|
||||
) -> Self {
|
||||
let sess = ssh_connect();
|
||||
let mut channel = sess.channel_session().unwrap();
|
||||
let vte_parser = vte::Parser::new();
|
||||
let terminal_output = TerminalPane::new(0, win_size, Palette::default(), 0); // 0 is the pane index
|
||||
setup_remote_environment(&mut channel, win_size);
|
||||
start_zellij_without_frames(&mut channel);
|
||||
RemoteRunner {
|
||||
steps: vec![],
|
||||
channel,
|
||||
terminal_output,
|
||||
vte_parser,
|
||||
session_name,
|
||||
test_name,
|
||||
currently_running_step: None,
|
||||
current_step_index: 0,
|
||||
retries_left: 3,
|
||||
win_size,
|
||||
layout_file_name: None,
|
||||
without_frames: true,
|
||||
}
|
||||
}
|
||||
pub fn new_with_layout(
|
||||
@ -232,12 +268,11 @@ impl RemoteRunner {
|
||||
layout_file_name: &'static str,
|
||||
session_name: Option<String>,
|
||||
) -> Self {
|
||||
// let layout_file_name = local_layout_path.file_name().unwrap();
|
||||
let remote_path = Path::new(ZELLIJ_LAYOUT_PATH).join(layout_file_name); // TODO: not hardcoded
|
||||
let remote_path = Path::new(ZELLIJ_LAYOUT_PATH).join(layout_file_name);
|
||||
let sess = ssh_connect();
|
||||
let mut channel = sess.channel_session().unwrap();
|
||||
let vte_parser = vte::Parser::new();
|
||||
let terminal_output = TerminalPane::new(0, win_size, Palette::default());
|
||||
let terminal_output = TerminalPane::new(0, win_size, Palette::default(), 0); // 0 is the pane index
|
||||
setup_remote_environment(&mut channel, win_size);
|
||||
start_zellij_with_layout(
|
||||
&mut channel,
|
||||
@ -256,6 +291,7 @@ impl RemoteRunner {
|
||||
retries_left: 3,
|
||||
win_size,
|
||||
layout_file_name: Some(layout_file_name),
|
||||
without_frames: false,
|
||||
}
|
||||
}
|
||||
pub fn add_step(mut self, step: Step) -> Self {
|
||||
@ -315,6 +351,13 @@ impl RemoteRunner {
|
||||
new_runner.replace_steps(self.steps.clone());
|
||||
drop(std::mem::replace(self, new_runner));
|
||||
self.run_all_steps()
|
||||
} else if self.without_frames {
|
||||
let mut new_runner =
|
||||
RemoteRunner::new_without_frames(self.test_name, self.win_size, session_name);
|
||||
new_runner.retries_left = self.retries_left - 1;
|
||||
new_runner.replace_steps(self.steps.clone());
|
||||
drop(std::mem::replace(self, new_runner));
|
||||
self.run_all_steps()
|
||||
} else {
|
||||
let mut new_runner = RemoteRunner::new(self.test_name, self.win_size, session_name);
|
||||
new_runner.retries_left = self.retries_left - 1;
|
||||
|
@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
$ █ │$
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
───────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
$
|
||||
|
||||
|
||||
|
||||
|
||||
┌ Pane #1 ─────────────┐┌ Pane #2 ─────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│$ █ ││$ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
└──────────────────────┘└──────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
┌ Pane #3 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│$ │
|
||||
│ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
|
||||
|
||||
────────
|
||||
$ Hi!█
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
─ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
$ █
|
||||
|
||||
|
||||
|
@ -1,29 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ I am some text█
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
|
||||
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
|
||||
│$ ││$ I am some text█ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
||||
|
@ -4,26 +4,26 @@ expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ █ │$
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
|
||||
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
|
||||
│$ █ ││$ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
─ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
$ nabc█
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1 Tab #2
|
||||
|
||||
─ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
$ █
|
||||
|
||||
|
||||
|
@ -1,29 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ █
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
|
||||
┌ Pane #1 ───────────────────────────────────────┐┌ Pane #2 ───────────────────────────────────────────────────────────┐
|
||||
│$ ││$ █ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
└────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
||||
|
@ -1,29 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ █
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
|
||||
┌ Pane #1 ─────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
|
||||
│$ ││$ █ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
└──────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
||||
|
@ -1,29 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ line1 000000000000000000000000000000000000000000000000000
|
||||
│line2 00000000000000000000000000000000000000000000000000000
|
||||
│line3 00000000000000000000000000000000000000000000000000000
|
||||
│line4 00000000000000000000000000000000000000000000000000000
|
||||
│line5 00000000000000000000000000000000000000000000000000000
|
||||
│line6 00000000000000000000000000000000000000000000000000000
|
||||
│line7 00000000000000000000000000000000000000000000000000000
|
||||
│line8 00000000000000000000000000000000000000000000000000000
|
||||
│line9 00000000000000000000000000000000000000000000000000000
|
||||
│line10 0000000000000000000000000000000000000000000000000000
|
||||
│line11 0000000000000000000000000000000000000000000000000000
|
||||
│line12 0000000000000000000000000000000000000000000000000000
|
||||
│line13 0000000000000000000000000000000000000000000000000000
|
||||
│line14 0000000000000000000000000000000000000000000000000000
|
||||
│line15 0000000000000000000000000000000000000000000000000000
|
||||
│line16 0000000000000000000000000000000000000000000000000000
|
||||
│line17 0000000000000000000000000000000000000000000000000000
|
||||
│line18 0000000000000000000000000000000000000000000000000000
|
||||
│line19 000000000000000000000000000000000000000000000000000█
|
||||
|
||||
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 1/1 ┐
|
||||
│$ ││$ line1 00000000000000000000000000000000000000000000000000│
|
||||
│ ││line2 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line3 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line4 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line5 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line6 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line7 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line8 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line9 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line10 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line11 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line12 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line13 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line14 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line15 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line16 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line17 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line18 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line19 00000000000000000000000000000000000000000000000000█│
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
<↓↑> Scroll / <PgUp/PgDn> Scroll Page / <ENTER> Select pane
|
||||
|
@ -4,26 +4,26 @@ expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ line1 000000000000000000000000000000000000000000000000000
|
||||
│line2 00000000000000000000000000000000000000000000000000000
|
||||
│line3 00000000000000000000000000000000000000000000000000000
|
||||
│line4 00000000000000000000000000000000000000000000000000000
|
||||
│line5 00000000000000000000000000000000000000000000000000000
|
||||
│line6 00000000000000000000000000000000000000000000000000000
|
||||
│line7 00000000000000000000000000000000000000000000000000000
|
||||
│line8 00000000000000000000000000000000000000000000000000000
|
||||
│line9 00000000000000000000000000000000000000000000000000000
|
||||
│line10 0000000000000000000000000000000000000000000000000000
|
||||
│line11 0000000000000000000000000000000000000000000000000000
|
||||
│line12 0000000000000000000000000000000000000000000000000000
|
||||
│line13 0000000000000000000000000000000000000000000000000000
|
||||
│line14 0000000000000000000000000000000000000000000000000000
|
||||
│line15 0000000000000000000000000000000000000000000000000000
|
||||
│line16 0000000000000000000000000000000000000000000000000000
|
||||
│line17 0000000000000000000000000000000000000000000000000000
|
||||
│line18 0000000000000000000000000000000000000000000000000000
|
||||
│line19 000000000000000000000000000000000000000000000000000█
|
||||
|
||||
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 1/1 ┐
|
||||
│$ ││$ line1 00000000000000000000000000000000000000000000000000│
|
||||
│ ││line2 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line3 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line4 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line5 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line6 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line7 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line8 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line9 0000000000000000000000000000000000000000000000000000│
|
||||
│ ││line10 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line11 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line12 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line13 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line14 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line15 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line16 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line17 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line18 000000000000000000000000000000000000000000000000000│
|
||||
│ ││line19 00000000000000000000000000000000000000000000000000█│
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
||||
|
@ -1,29 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ █
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
|
||||
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
|
||||
│$ ││$ █ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
||||
|
@ -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.
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
─ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
$ █
|
||||
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
source: src/tests/e2e/cases.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
─ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
$ █
|
||||
|
||||
|
||||
@ -23,7 +25,5 @@ $ █
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
||||
|
@ -10,7 +10,8 @@ use std::{
|
||||
use zellij_utils::{position::Position, vte, zellij_tile};
|
||||
|
||||
const TABSTOP_WIDTH: usize = 8; // TODO: is this always right?
|
||||
const SCROLL_BACK: usize = 10_000;
|
||||
pub const SCROLL_BACK: usize = 10_000;
|
||||
pub const MAX_TITLE_STACK_SIZE: usize = 1000;
|
||||
|
||||
use vte::{Params, Perform};
|
||||
use zellij_tile::data::{Palette, PaletteColor};
|
||||
@ -308,6 +309,7 @@ pub struct Grid {
|
||||
preceding_char: Option<TerminalCharacter>,
|
||||
colors: Palette,
|
||||
output_buffer: OutputBuffer,
|
||||
title_stack: Vec<String>,
|
||||
pub should_render: bool,
|
||||
pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "[D")
|
||||
pub erasure_mode: bool, // ERM
|
||||
@ -318,6 +320,7 @@ pub struct Grid {
|
||||
pub height: usize,
|
||||
pub pending_messages_to_pty: Vec<Vec<u8>>,
|
||||
pub selection: Selection,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl Debug for Grid {
|
||||
@ -358,6 +361,8 @@ impl Grid {
|
||||
colors,
|
||||
output_buffer: Default::default(),
|
||||
selection: Default::default(),
|
||||
title_stack: vec![],
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
pub fn render_full_viewport(&mut self) {
|
||||
@ -404,6 +409,23 @@ impl Grid {
|
||||
pub fn cursor_shape(&self) -> CursorShape {
|
||||
self.cursor.get_shape()
|
||||
}
|
||||
pub fn scrollback_position_and_length(&self) -> (usize, usize) {
|
||||
// (position, length)
|
||||
let mut scrollback_buffer_count = 0;
|
||||
for row in self.lines_above.iter() {
|
||||
let row_width = row.width();
|
||||
// rows in lines_above are unwrapped, so we need to account for that
|
||||
if row_width > self.width {
|
||||
scrollback_buffer_count += (row_width as f64 / self.width as f64).ceil() as usize;
|
||||
} else {
|
||||
scrollback_buffer_count += 1;
|
||||
}
|
||||
}
|
||||
(
|
||||
self.lines_below.len(),
|
||||
(scrollback_buffer_count + self.lines_below.len()),
|
||||
)
|
||||
}
|
||||
fn set_horizontal_tabstop(&mut self) {
|
||||
self.horizontal_tabstops.insert(self.cursor.x);
|
||||
}
|
||||
@ -475,8 +497,17 @@ impl Grid {
|
||||
if !self.lines_above.is_empty() && self.viewport.len() == self.height {
|
||||
let line_to_push_down = self.viewport.pop().unwrap();
|
||||
self.lines_below.insert(0, line_to_push_down);
|
||||
let line_to_insert_at_viewport_top = self.lines_above.pop_back().unwrap();
|
||||
self.viewport.insert(0, line_to_insert_at_viewport_top);
|
||||
|
||||
transfer_rows_down(
|
||||
&mut self.lines_above,
|
||||
&mut self.viewport,
|
||||
1,
|
||||
None,
|
||||
Some(self.width),
|
||||
);
|
||||
|
||||
// let line_to_insert_at_viewport_top = self.lines_above.pop_back().unwrap();
|
||||
// self.viewport.insert(0, line_to_insert_at_viewport_top);
|
||||
self.selection.move_down(1);
|
||||
}
|
||||
self.output_buffer.update_all_lines();
|
||||
@ -497,6 +528,24 @@ impl Grid {
|
||||
self.output_buffer.update_all_lines();
|
||||
}
|
||||
}
|
||||
fn force_change_size(&mut self, new_rows: usize, new_columns: usize) {
|
||||
// this is an ugly hack - it's here because sometimes we need to change_size to the
|
||||
// existing size (eg. when resizing an alternative_grid to the current height/width) and
|
||||
// the change_size method is a no-op in that case. Should be fixed by making the
|
||||
// change_size method atomic
|
||||
let intermediate_rows = if new_rows == self.height {
|
||||
new_rows + 1
|
||||
} else {
|
||||
new_rows
|
||||
};
|
||||
let intermediate_columns = if new_columns == self.width {
|
||||
new_columns + 1
|
||||
} else {
|
||||
new_columns
|
||||
};
|
||||
self.change_size(intermediate_rows, intermediate_columns);
|
||||
self.change_size(new_rows, new_columns);
|
||||
}
|
||||
pub fn change_size(&mut self, new_rows: usize, new_columns: usize) {
|
||||
self.selection.reset();
|
||||
if new_columns != self.width {
|
||||
@ -1200,7 +1249,7 @@ impl Grid {
|
||||
let empty_row = Row::from_columns(vec![EMPTY_TERMINAL_CHARACTER; self.width]);
|
||||
|
||||
// get the row from lines_above, viewport, or lines below depending on index
|
||||
let row = if l < 0 {
|
||||
let row = if l < 0 && self.lines_above.len() > l.abs() as usize {
|
||||
let offset_from_end = l.abs();
|
||||
&self.lines_above[self
|
||||
.lines_above
|
||||
@ -1211,8 +1260,12 @@ impl Grid {
|
||||
} else if (l as usize) < self.height {
|
||||
// index is in viewport but there is no line
|
||||
&empty_row
|
||||
} else {
|
||||
} else if self.lines_below.len() > (l as usize).saturating_sub(self.viewport.len()) {
|
||||
&self.lines_below[(l as usize) - self.viewport.len()]
|
||||
} else {
|
||||
// can't find the line, this probably it's on the pane border
|
||||
// is on the pane border
|
||||
continue;
|
||||
};
|
||||
|
||||
let excess_width = row.excess_width();
|
||||
@ -1242,6 +1295,22 @@ impl Grid {
|
||||
self.output_buffer.update_line(l as usize);
|
||||
}
|
||||
}
|
||||
fn set_title(&mut self, title: String) {
|
||||
self.title = Some(title);
|
||||
}
|
||||
fn push_current_title_to_stack(&mut self) {
|
||||
if self.title_stack.len() > MAX_TITLE_STACK_SIZE {
|
||||
self.title_stack.remove(0);
|
||||
}
|
||||
if let Some(title) = self.title.as_ref() {
|
||||
self.title_stack.push(title.clone());
|
||||
}
|
||||
}
|
||||
fn pop_title_from_stack(&mut self) {
|
||||
if let Some(popped_title) = self.title_stack.pop() {
|
||||
self.title = Some(popped_title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Perform for Grid {
|
||||
@ -1311,7 +1380,7 @@ impl Perform for Grid {
|
||||
// Set window title.
|
||||
b"0" | b"2" => {
|
||||
if params.len() >= 2 {
|
||||
let _title = params[1..]
|
||||
let title = params[1..]
|
||||
.iter()
|
||||
.flat_map(|x| str::from_utf8(x))
|
||||
.collect::<Vec<&str>>()
|
||||
@ -1319,6 +1388,7 @@ impl Perform for Grid {
|
||||
.trim()
|
||||
.to_owned();
|
||||
// TBD: do something with title?
|
||||
self.set_title(title);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1521,7 +1591,7 @@ impl Perform for Grid {
|
||||
}
|
||||
self.alternative_lines_above_viewport_and_cursor = None;
|
||||
self.clear_viewport_before_rendering = true;
|
||||
self.change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
|
||||
self.force_change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
|
||||
self.mark_for_rerender();
|
||||
}
|
||||
Some(25) => {
|
||||
@ -1758,10 +1828,10 @@ impl Perform for Grid {
|
||||
.push(text_area_report.as_bytes().to_vec());
|
||||
}
|
||||
22 => {
|
||||
// TODO: push title
|
||||
self.push_current_title_to_stack();
|
||||
}
|
||||
23 => {
|
||||
// TODO: pop title
|
||||
self.pop_title_from_stack();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -2,10 +2,13 @@ use std::sync::mpsc::channel;
|
||||
use std::time::Instant;
|
||||
use std::unimplemented;
|
||||
|
||||
use crate::panes::PaneId;
|
||||
use crate::panes::{PaneDecoration, PaneId};
|
||||
use crate::pty::VteBytes;
|
||||
use crate::tab::Pane;
|
||||
use crate::ui::pane_boundaries_frame::PaneBoundariesFrame;
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
use zellij_utils::shared::ansi_len;
|
||||
use zellij_utils::zellij_tile::prelude::PaletteColor;
|
||||
use zellij_utils::{channels::SenderWithContext, pane_size::PositionAndSize};
|
||||
|
||||
pub(crate) struct PluginPane {
|
||||
@ -15,8 +18,11 @@ pub(crate) struct PluginPane {
|
||||
pub invisible_borders: bool,
|
||||
pub position_and_size: PositionAndSize,
|
||||
pub position_and_size_override: Option<PositionAndSize>,
|
||||
pub content_position_and_size: PositionAndSize,
|
||||
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
pub active_at: Instant,
|
||||
pub pane_title: String,
|
||||
pane_decoration: PaneDecoration,
|
||||
}
|
||||
|
||||
impl PluginPane {
|
||||
@ -24,6 +30,7 @@ impl PluginPane {
|
||||
pid: u32,
|
||||
position_and_size: PositionAndSize,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
title: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
pid,
|
||||
@ -34,8 +41,48 @@ impl PluginPane {
|
||||
position_and_size_override: None,
|
||||
send_plugin_instructions,
|
||||
active_at: Instant::now(),
|
||||
pane_decoration: PaneDecoration::ContentOffset((0, 0)),
|
||||
content_position_and_size: position_and_size,
|
||||
pane_title: title,
|
||||
}
|
||||
}
|
||||
pub fn get_content_x(&self) -> usize {
|
||||
self.get_content_posision_and_size().x
|
||||
}
|
||||
pub fn get_content_y(&self) -> usize {
|
||||
self.get_content_posision_and_size().y
|
||||
}
|
||||
pub fn get_content_columns(&self) -> usize {
|
||||
// content columns might differ from the pane's columns if the pane has a frame
|
||||
// in that case they would be 2 less
|
||||
self.get_content_posision_and_size().cols
|
||||
}
|
||||
pub fn get_content_rows(&self) -> usize {
|
||||
// content rows might differ from the pane's rows if the pane has a frame
|
||||
// in that case they would be 2 less
|
||||
self.get_content_posision_and_size().rows
|
||||
}
|
||||
pub fn get_content_posision_and_size(&self) -> PositionAndSize {
|
||||
self.content_position_and_size
|
||||
}
|
||||
fn redistribute_space(&mut self) {
|
||||
let position_and_size = self
|
||||
.position_and_size_override
|
||||
.unwrap_or_else(|| self.position_and_size());
|
||||
match &mut self.pane_decoration {
|
||||
PaneDecoration::BoundariesFrame(boundaries_frame) => {
|
||||
boundaries_frame.change_pos_and_size(position_and_size);
|
||||
self.content_position_and_size = boundaries_frame.content_position_and_size();
|
||||
}
|
||||
PaneDecoration::ContentOffset((content_columns_offset, content_rows_offset)) => {
|
||||
self.content_position_and_size = position_and_size;
|
||||
self.content_position_and_size.cols =
|
||||
position_and_size.cols - *content_columns_offset;
|
||||
self.content_position_and_size.rows = position_and_size.rows - *content_rows_offset;
|
||||
}
|
||||
};
|
||||
self.set_should_render(true);
|
||||
}
|
||||
}
|
||||
|
||||
impl Pane for PluginPane {
|
||||
@ -62,13 +109,20 @@ impl Pane for PluginPane {
|
||||
.unwrap_or(self.position_and_size)
|
||||
.cols
|
||||
}
|
||||
fn get_content_columns(&self) -> usize {
|
||||
self.get_content_columns()
|
||||
}
|
||||
fn get_content_rows(&self) -> usize {
|
||||
self.get_content_rows()
|
||||
}
|
||||
fn reset_size_and_position_override(&mut self) {
|
||||
self.position_and_size_override = None;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
|
||||
self.position_and_size = *position_and_size;
|
||||
self.should_render = true;
|
||||
self.redistribute_space();
|
||||
}
|
||||
// FIXME: This is obviously a bit outdated and needs the x and y moved into `size`
|
||||
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
|
||||
@ -80,7 +134,7 @@ impl Pane for PluginPane {
|
||||
..Default::default()
|
||||
};
|
||||
self.position_and_size_override = Some(position_and_size_override);
|
||||
self.should_render = true;
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn handle_pty_bytes(&mut self, _event: VteBytes) {
|
||||
unimplemented!()
|
||||
@ -103,6 +157,11 @@ impl Pane for PluginPane {
|
||||
fn set_should_render(&mut self, should_render: bool) {
|
||||
self.should_render = should_render;
|
||||
}
|
||||
fn set_should_render_boundaries(&mut self, should_render: bool) {
|
||||
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
|
||||
boundaries_frame.set_should_render(should_render);
|
||||
}
|
||||
}
|
||||
fn selectable(&self) -> bool {
|
||||
self.selectable
|
||||
}
|
||||
@ -127,19 +186,70 @@ impl Pane for PluginPane {
|
||||
// is more performant, it causes some problems when the pane to the left should be
|
||||
// rendered and has wide characters (eg. Chinese characters or emoji)
|
||||
// as a (hopefully) temporary hack, we render all panes until we find a better solution
|
||||
let mut vte_output = String::new();
|
||||
let (buf_tx, buf_rx) = channel();
|
||||
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Render(
|
||||
buf_tx,
|
||||
self.pid,
|
||||
self.rows(),
|
||||
self.columns(),
|
||||
self.get_content_rows(),
|
||||
self.get_content_columns(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
self.should_render = false;
|
||||
Some(buf_rx.recv().unwrap())
|
||||
let contents = buf_rx.recv().unwrap();
|
||||
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
|
||||
if let Some(boundaries_frame_vte) = boundaries_frame.render() {
|
||||
vte_output.push_str(&boundaries_frame_vte);
|
||||
}
|
||||
}
|
||||
for (index, line) in contents.lines().enumerate() {
|
||||
let actual_len = ansi_len(line);
|
||||
let line_to_print = if actual_len > self.get_content_columns() {
|
||||
let mut line = String::from(line);
|
||||
line.truncate(self.get_content_columns());
|
||||
line
|
||||
} else {
|
||||
[
|
||||
line,
|
||||
&str::repeat(" ", self.get_content_columns() - ansi_len(line)),
|
||||
]
|
||||
.concat()
|
||||
};
|
||||
|
||||
vte_output.push_str(&format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
self.get_content_y() + 1 + index,
|
||||
self.get_content_x() + 1,
|
||||
line_to_print,
|
||||
)); // goto row/col and reset styles
|
||||
let line_len = line_to_print.len();
|
||||
if line_len < self.get_content_columns() {
|
||||
// pad line
|
||||
for _ in line_len..self.get_content_columns() {
|
||||
vte_output.push(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
let total_line_count = contents.lines().count();
|
||||
if total_line_count < self.get_content_rows() {
|
||||
// pad lines
|
||||
for line_index in total_line_count..self.get_content_rows() {
|
||||
let x = self.get_content_x();
|
||||
let y = self.get_content_y();
|
||||
vte_output.push_str(&format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m",
|
||||
y + line_index + 1,
|
||||
x + 1
|
||||
)); // goto row/col and reset styles
|
||||
for _col_index in 0..self.get_content_columns() {
|
||||
vte_output.push(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(vte_output)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -150,50 +260,66 @@ impl Pane for PluginPane {
|
||||
fn reduce_height_down(&mut self, count: usize) {
|
||||
self.position_and_size.y += count;
|
||||
self.position_and_size.rows -= count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn increase_height_down(&mut self, count: usize) {
|
||||
self.position_and_size.rows += count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn increase_height_up(&mut self, count: usize) {
|
||||
self.position_and_size.y -= count;
|
||||
self.position_and_size.rows += count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn reduce_height_up(&mut self, count: usize) {
|
||||
self.position_and_size.rows -= count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn reduce_width_right(&mut self, count: usize) {
|
||||
self.position_and_size.x += count;
|
||||
self.position_and_size.cols -= count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn reduce_width_left(&mut self, count: usize) {
|
||||
self.position_and_size.cols -= count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn increase_width_left(&mut self, count: usize) {
|
||||
self.position_and_size.x -= count;
|
||||
self.position_and_size.cols += count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn increase_width_right(&mut self, count: usize) {
|
||||
self.position_and_size.cols += count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn push_down(&mut self, count: usize) {
|
||||
self.position_and_size.y += count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn push_right(&mut self, count: usize) {
|
||||
self.position_and_size.x += count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn pull_left(&mut self, count: usize) {
|
||||
self.position_and_size.x -= count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn pull_up(&mut self, count: usize) {
|
||||
self.position_and_size.y -= count;
|
||||
self.redistribute_space();
|
||||
self.should_render = true;
|
||||
}
|
||||
fn scroll_up(&mut self, _count: usize) {
|
||||
//unimplemented!()
|
||||
@ -231,4 +357,61 @@ impl Pane for PluginPane {
|
||||
fn set_active_at(&mut self, time: Instant) {
|
||||
self.active_at = time;
|
||||
}
|
||||
fn set_boundary_color(&mut self, color: Option<PaletteColor>) {
|
||||
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
|
||||
boundaries_frame.set_color(color);
|
||||
}
|
||||
}
|
||||
fn offset_content_columns(&mut self, by: usize) {
|
||||
if !self.selectable {
|
||||
return;
|
||||
}
|
||||
if let PaneDecoration::ContentOffset(content_offset) = &mut self.pane_decoration {
|
||||
content_offset.0 = by;
|
||||
} else {
|
||||
self.pane_decoration = PaneDecoration::ContentOffset((by, 0));
|
||||
}
|
||||
self.redistribute_space();
|
||||
self.set_should_render(true);
|
||||
}
|
||||
fn offset_content_rows(&mut self, by: usize) {
|
||||
if !self.selectable {
|
||||
return;
|
||||
}
|
||||
if let PaneDecoration::ContentOffset(content_offset) = &mut self.pane_decoration {
|
||||
content_offset.1 = by;
|
||||
} else {
|
||||
self.pane_decoration = PaneDecoration::ContentOffset((0, by));
|
||||
}
|
||||
self.redistribute_space();
|
||||
self.set_should_render(true);
|
||||
}
|
||||
fn show_boundaries_frame(&mut self, should_render_only_title: bool) {
|
||||
if !self.selectable {
|
||||
return;
|
||||
}
|
||||
let position_and_size = self
|
||||
.position_and_size_override
|
||||
.unwrap_or(self.position_and_size);
|
||||
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
|
||||
boundaries_frame.render_only_title(should_render_only_title);
|
||||
self.content_position_and_size = boundaries_frame.content_position_and_size();
|
||||
} else {
|
||||
let mut boundaries_frame =
|
||||
PaneBoundariesFrame::new(position_and_size, self.pane_title.clone());
|
||||
boundaries_frame.render_only_title(should_render_only_title);
|
||||
self.content_position_and_size = boundaries_frame.content_position_and_size();
|
||||
self.pane_decoration = PaneDecoration::BoundariesFrame(boundaries_frame);
|
||||
}
|
||||
self.redistribute_space();
|
||||
self.set_should_render(true);
|
||||
}
|
||||
fn remove_boundaries_frame(&mut self) {
|
||||
if !self.selectable {
|
||||
return;
|
||||
}
|
||||
self.pane_decoration = PaneDecoration::ContentOffset((0, 0));
|
||||
self.redistribute_space();
|
||||
self.set_should_render(true);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use std::fmt::Debug;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::time::{self, Instant};
|
||||
use zellij_tile::data::Palette;
|
||||
|
||||
use zellij_utils::pane_size::PositionAndSize;
|
||||
|
||||
use crate::panes::AnsiCode;
|
||||
@ -20,22 +21,32 @@ use crate::tab::Pane;
|
||||
|
||||
pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10;
|
||||
|
||||
use crate::ui::pane_boundaries_frame::PaneBoundariesFrame;
|
||||
|
||||
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
|
||||
pub enum PaneId {
|
||||
Terminal(RawFd),
|
||||
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
|
||||
}
|
||||
|
||||
pub enum PaneDecoration {
|
||||
BoundariesFrame(PaneBoundariesFrame),
|
||||
ContentOffset((usize, usize)), // (columns, rows)
|
||||
}
|
||||
|
||||
pub struct TerminalPane {
|
||||
pub grid: Grid,
|
||||
pub pid: RawFd,
|
||||
pub selectable: bool,
|
||||
pub position_and_size: PositionAndSize,
|
||||
pub position_and_size_override: Option<PositionAndSize>,
|
||||
position_and_size: PositionAndSize,
|
||||
position_and_size_override: Option<PositionAndSize>,
|
||||
pub active_at: Instant,
|
||||
pub colors: Palette,
|
||||
vte_parser: vte::Parser,
|
||||
selection_scrolled_at: time::Instant,
|
||||
content_position_and_size: PositionAndSize,
|
||||
pane_title: String,
|
||||
pane_decoration: PaneDecoration,
|
||||
}
|
||||
|
||||
impl Pane for TerminalPane {
|
||||
@ -51,24 +62,29 @@ impl Pane for TerminalPane {
|
||||
fn columns(&self) -> usize {
|
||||
self.get_columns()
|
||||
}
|
||||
fn get_content_columns(&self) -> usize {
|
||||
self.get_content_columns()
|
||||
}
|
||||
fn get_content_rows(&self) -> usize {
|
||||
self.get_content_rows()
|
||||
}
|
||||
fn reset_size_and_position_override(&mut self) {
|
||||
self.position_and_size_override = None;
|
||||
self.reflow_lines();
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
|
||||
self.position_and_size = *position_and_size;
|
||||
self.reflow_lines();
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
|
||||
let position_and_size_override = PositionAndSize {
|
||||
self.position_and_size_override = Some(PositionAndSize {
|
||||
x,
|
||||
y,
|
||||
rows: size.rows,
|
||||
cols: size.cols,
|
||||
..Default::default()
|
||||
};
|
||||
self.position_and_size_override = Some(position_and_size_override);
|
||||
self.reflow_lines();
|
||||
});
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn handle_pty_bytes(&mut self, bytes: VteBytes) {
|
||||
for byte in bytes.iter() {
|
||||
@ -78,7 +94,17 @@ impl Pane for TerminalPane {
|
||||
}
|
||||
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
||||
// (x, y)
|
||||
self.grid.cursor_coordinates()
|
||||
let (x_offset, y_offset) = match &self.pane_decoration {
|
||||
PaneDecoration::BoundariesFrame(boundries_frame) => {
|
||||
let (content_columns_offset, content_rows_offset) =
|
||||
boundries_frame.content_offset();
|
||||
(content_columns_offset, content_rows_offset)
|
||||
}
|
||||
PaneDecoration::ContentOffset(_) => (0, 0),
|
||||
};
|
||||
self.grid
|
||||
.cursor_coordinates()
|
||||
.map(|(x, y)| (x + x_offset, y + y_offset))
|
||||
}
|
||||
fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8> {
|
||||
// there are some cases in which the terminal state means that input sent to it
|
||||
@ -134,7 +160,14 @@ impl Pane for TerminalPane {
|
||||
fn set_should_render(&mut self, should_render: bool) {
|
||||
self.grid.should_render = should_render;
|
||||
}
|
||||
fn set_should_render_boundaries(&mut self, should_render: bool) {
|
||||
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
|
||||
boundaries_frame.set_should_render(should_render);
|
||||
}
|
||||
}
|
||||
fn render_full_viewport(&mut self) {
|
||||
// this marks the pane for a full re-render, rather than just rendering the
|
||||
// diff as it usually does with the OutputBuffer
|
||||
self.grid.render_full_viewport();
|
||||
}
|
||||
fn selectable(&self) -> bool {
|
||||
@ -173,10 +206,10 @@ impl Pane for TerminalPane {
|
||||
}
|
||||
self.grid.clear_viewport_before_rendering = false;
|
||||
}
|
||||
let max_width = self.columns();
|
||||
let max_width = self.get_content_columns();
|
||||
for character_chunk in self.grid.read_changes() {
|
||||
let pane_x = self.get_x();
|
||||
let pane_y = self.get_y();
|
||||
let pane_x = self.get_content_x();
|
||||
let pane_y = self.get_content_y();
|
||||
let chunk_absolute_x = pane_x + character_chunk.x;
|
||||
let chunk_absolute_y = pane_y + character_chunk.y;
|
||||
let terminal_characters = character_chunk.terminal_characters;
|
||||
@ -212,6 +245,13 @@ impl Pane for TerminalPane {
|
||||
}
|
||||
character_styles.clear();
|
||||
}
|
||||
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
|
||||
boundaries_frame.update_scroll(self.grid.scrollback_position_and_length());
|
||||
boundaries_frame.update_title(self.grid.title.as_ref());
|
||||
if let Some(boundaries_frame_vte) = boundaries_frame.render() {
|
||||
vte_output.push_str(&boundaries_frame_vte);
|
||||
}
|
||||
}
|
||||
self.set_should_render(false);
|
||||
Some(vte_output)
|
||||
} else {
|
||||
@ -224,50 +264,54 @@ impl Pane for TerminalPane {
|
||||
fn reduce_height_down(&mut self, count: usize) {
|
||||
self.position_and_size.y += count;
|
||||
self.position_and_size.rows -= count;
|
||||
self.reflow_lines();
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn increase_height_down(&mut self, count: usize) {
|
||||
self.position_and_size.rows += count;
|
||||
self.reflow_lines();
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn increase_height_up(&mut self, count: usize) {
|
||||
self.position_and_size.y -= count;
|
||||
self.position_and_size.rows += count;
|
||||
self.reflow_lines();
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn reduce_height_up(&mut self, count: usize) {
|
||||
self.position_and_size.rows -= count;
|
||||
self.reflow_lines();
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn reduce_width_right(&mut self, count: usize) {
|
||||
self.position_and_size.x += count;
|
||||
self.position_and_size.cols -= count;
|
||||
self.reflow_lines();
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn reduce_width_left(&mut self, count: usize) {
|
||||
self.position_and_size.cols -= count;
|
||||
self.reflow_lines();
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn increase_width_left(&mut self, count: usize) {
|
||||
self.position_and_size.x -= count;
|
||||
self.position_and_size.cols += count;
|
||||
self.reflow_lines();
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn increase_width_right(&mut self, count: usize) {
|
||||
self.position_and_size.cols += count;
|
||||
self.reflow_lines();
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn push_down(&mut self, count: usize) {
|
||||
self.position_and_size.y += count;
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn push_right(&mut self, count: usize) {
|
||||
self.position_and_size.x += count;
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn pull_left(&mut self, count: usize) {
|
||||
self.position_and_size.x -= count;
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn pull_up(&mut self, count: usize) {
|
||||
self.position_and_size.y -= count;
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn scroll_up(&mut self, count: usize) {
|
||||
self.grid.move_viewport_up(count);
|
||||
@ -337,12 +381,69 @@ impl Pane for TerminalPane {
|
||||
fn get_selected_text(&self) -> Option<String> {
|
||||
self.grid.get_selected_text()
|
||||
}
|
||||
|
||||
fn set_boundary_color(&mut self, color: Option<PaletteColor>) {
|
||||
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
|
||||
if boundaries_frame.color != color {
|
||||
boundaries_frame.set_color(color);
|
||||
self.set_should_render(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn relative_position(&self, position_on_screen: &Position) -> Position {
|
||||
let pane_position_and_size = self.get_content_posision_and_size();
|
||||
position_on_screen.relative_to(&pane_position_and_size)
|
||||
}
|
||||
fn offset_content_columns(&mut self, by: usize) {
|
||||
if let PaneDecoration::ContentOffset(content_offset) = &mut self.pane_decoration {
|
||||
content_offset.0 = by;
|
||||
} else {
|
||||
self.pane_decoration = PaneDecoration::ContentOffset((by, 0));
|
||||
}
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn offset_content_rows(&mut self, by: usize) {
|
||||
if let PaneDecoration::ContentOffset(content_offset) = &mut self.pane_decoration {
|
||||
content_offset.1 = by;
|
||||
} else {
|
||||
self.pane_decoration = PaneDecoration::ContentOffset((0, by));
|
||||
}
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn show_boundaries_frame(&mut self, only_title: bool) {
|
||||
let position_and_size = self
|
||||
.position_and_size_override
|
||||
.unwrap_or(self.position_and_size);
|
||||
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
|
||||
boundaries_frame.render_only_title(only_title);
|
||||
self.content_position_and_size = boundaries_frame.content_position_and_size();
|
||||
} else {
|
||||
let mut boundaries_frame =
|
||||
PaneBoundariesFrame::new(position_and_size, self.pane_title.clone());
|
||||
boundaries_frame.render_only_title(only_title);
|
||||
self.content_position_and_size = boundaries_frame.content_position_and_size();
|
||||
self.pane_decoration = PaneDecoration::BoundariesFrame(boundaries_frame);
|
||||
}
|
||||
self.redistribute_space();
|
||||
}
|
||||
fn remove_boundaries_frame(&mut self) {
|
||||
self.pane_decoration = PaneDecoration::ContentOffset((0, 0));
|
||||
self.redistribute_space();
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalPane {
|
||||
pub fn new(pid: RawFd, position_and_size: PositionAndSize, palette: Palette) -> TerminalPane {
|
||||
pub fn new(
|
||||
pid: RawFd,
|
||||
position_and_size: PositionAndSize,
|
||||
palette: Palette,
|
||||
pane_position: usize,
|
||||
) -> TerminalPane {
|
||||
let initial_pane_title = format!("Pane #{}", pane_position);
|
||||
let grid = Grid::new(position_and_size.rows, position_and_size.cols, palette);
|
||||
TerminalPane {
|
||||
pane_decoration: PaneDecoration::ContentOffset((0, 0)),
|
||||
content_position_and_size: position_and_size,
|
||||
pid,
|
||||
grid,
|
||||
selectable: true,
|
||||
@ -352,35 +453,55 @@ impl TerminalPane {
|
||||
active_at: Instant::now(),
|
||||
colors: palette,
|
||||
selection_scrolled_at: time::Instant::now(),
|
||||
pane_title: initial_pane_title,
|
||||
}
|
||||
}
|
||||
pub fn get_x(&self) -> usize {
|
||||
match self.position_and_size_override {
|
||||
match self.position_and_size_override.as_ref() {
|
||||
Some(position_and_size_override) => position_and_size_override.x,
|
||||
None => self.position_and_size.x as usize,
|
||||
None => self.position_and_size.x,
|
||||
}
|
||||
}
|
||||
pub fn get_y(&self) -> usize {
|
||||
match self.position_and_size_override {
|
||||
match self.position_and_size_override.as_ref() {
|
||||
Some(position_and_size_override) => position_and_size_override.y,
|
||||
None => self.position_and_size.y as usize,
|
||||
None => self.position_and_size.y,
|
||||
}
|
||||
}
|
||||
pub fn get_columns(&self) -> usize {
|
||||
match &self.position_and_size_override.as_ref() {
|
||||
match self.position_and_size_override.as_ref() {
|
||||
Some(position_and_size_override) => position_and_size_override.cols,
|
||||
None => self.position_and_size.cols as usize,
|
||||
None => self.position_and_size.cols,
|
||||
}
|
||||
}
|
||||
pub fn get_rows(&self) -> usize {
|
||||
match &self.position_and_size_override.as_ref() {
|
||||
match self.position_and_size_override.as_ref() {
|
||||
Some(position_and_size_override) => position_and_size_override.rows,
|
||||
None => self.position_and_size.rows as usize,
|
||||
None => self.position_and_size.rows,
|
||||
}
|
||||
}
|
||||
pub fn get_content_x(&self) -> usize {
|
||||
self.get_content_posision_and_size().x
|
||||
}
|
||||
pub fn get_content_y(&self) -> usize {
|
||||
self.get_content_posision_and_size().y
|
||||
}
|
||||
pub fn get_content_columns(&self) -> usize {
|
||||
// content columns might differ from the pane's columns if the pane has a frame
|
||||
// in that case they would be 2 less
|
||||
self.get_content_posision_and_size().cols
|
||||
}
|
||||
pub fn get_content_rows(&self) -> usize {
|
||||
// content rows might differ from the pane's rows if the pane has a frame
|
||||
// in that case they would be 2 less
|
||||
self.get_content_posision_and_size().rows
|
||||
}
|
||||
pub fn get_content_posision_and_size(&self) -> PositionAndSize {
|
||||
self.content_position_and_size
|
||||
}
|
||||
fn reflow_lines(&mut self) {
|
||||
let rows = self.get_rows();
|
||||
let columns = self.get_columns();
|
||||
let rows = self.get_content_rows();
|
||||
let columns = self.get_content_columns();
|
||||
self.grid.change_size(rows, columns);
|
||||
self.set_should_render(true);
|
||||
}
|
||||
@ -391,6 +512,24 @@ impl TerminalPane {
|
||||
// (x, y)
|
||||
self.grid.cursor_coordinates()
|
||||
}
|
||||
fn redistribute_space(&mut self) {
|
||||
let position_and_size = self
|
||||
.position_and_size_override
|
||||
.unwrap_or_else(|| self.position_and_size());
|
||||
match &mut self.pane_decoration {
|
||||
PaneDecoration::BoundariesFrame(boundaries_frame) => {
|
||||
boundaries_frame.change_pos_and_size(position_and_size);
|
||||
self.content_position_and_size = boundaries_frame.content_position_and_size();
|
||||
}
|
||||
PaneDecoration::ContentOffset((content_columns_offset, content_rows_offset)) => {
|
||||
self.content_position_and_size = position_and_size;
|
||||
self.content_position_and_size.cols =
|
||||
position_and_size.cols - *content_columns_offset;
|
||||
self.content_position_and_size.rows = position_and_size.rows - *content_rows_offset;
|
||||
}
|
||||
};
|
||||
self.reflow_lines();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -608,9 +608,9 @@ fn copy_selected_text_from_lines_below() {
|
||||
|
||||
grid.move_viewport_up(40);
|
||||
|
||||
grid.start_selection(&Position::new(63, 6));
|
||||
grid.start_selection(&Position::new(35, 6));
|
||||
// check for widechar, 📦 occupies columns 34, 35, and gets selected even if only the first column is selected
|
||||
grid.end_selection(Some(&Position::new(65, 35)));
|
||||
grid.end_selection(Some(&Position::new(37, 35)));
|
||||
let text = grid.get_selected_text();
|
||||
assert_eq!(
|
||||
text.unwrap(),
|
||||
|
@ -15,7 +15,7 @@ pub fn scrolling_inside_a_pane() {
|
||||
};
|
||||
let pid = 1;
|
||||
let palette = Palette::default();
|
||||
let mut terminal_pane = TerminalPane::new(pid, fake_win_size, palette);
|
||||
let mut terminal_pane = TerminalPane::new(pid, fake_win_size, palette, 0); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..30 {
|
||||
text_to_fill_pane.push_str(&format!("\rline {}\n", i + 1));
|
||||
|
@ -151,6 +151,12 @@ fn route_action(
|
||||
.send_to_screen(ScreenInstruction::ToggleActiveTerminalFullscreen)
|
||||
.unwrap();
|
||||
}
|
||||
Action::TogglePaneFrames => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::TogglePaneFrames)
|
||||
.unwrap();
|
||||
}
|
||||
Action::NewPane(direction) => {
|
||||
let shell = session.default_shell.clone();
|
||||
let pty_instr = match direction {
|
||||
|
@ -56,6 +56,7 @@ pub(crate) enum ScreenInstruction {
|
||||
ClearScroll,
|
||||
CloseFocusedPane,
|
||||
ToggleActiveTerminalFullscreen,
|
||||
TogglePaneFrames,
|
||||
SetSelectable(PaneId, bool, usize),
|
||||
SetFixedHeight(PaneId, usize, usize),
|
||||
SetFixedWidth(PaneId, usize, usize),
|
||||
@ -112,6 +113,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||
ScreenInstruction::ToggleActiveTerminalFullscreen => {
|
||||
ScreenContext::ToggleActiveTerminalFullscreen
|
||||
}
|
||||
ScreenInstruction::TogglePaneFrames => ScreenContext::TogglePaneFrames,
|
||||
ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable,
|
||||
ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders,
|
||||
ScreenInstruction::SetFixedHeight(..) => ScreenContext::SetFixedHeight,
|
||||
@ -153,6 +155,7 @@ pub(crate) struct Screen {
|
||||
mode_info: ModeInfo,
|
||||
colors: Palette,
|
||||
session_state: Arc<RwLock<SessionState>>,
|
||||
draw_pane_frames: bool,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
@ -163,6 +166,7 @@ impl Screen {
|
||||
max_panes: Option<usize>,
|
||||
mode_info: ModeInfo,
|
||||
session_state: Arc<RwLock<SessionState>>,
|
||||
draw_pane_frames: bool,
|
||||
) -> Self {
|
||||
Screen {
|
||||
bus,
|
||||
@ -173,6 +177,7 @@ impl Screen {
|
||||
tabs: BTreeMap::new(),
|
||||
mode_info,
|
||||
session_state,
|
||||
draw_pane_frames,
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,6 +198,7 @@ impl Screen {
|
||||
self.mode_info.clone(),
|
||||
self.colors,
|
||||
self.session_state.clone(),
|
||||
self.draw_pane_frames,
|
||||
);
|
||||
self.active_tab_index = Some(tab_index);
|
||||
self.tabs.insert(tab_index, tab);
|
||||
@ -359,6 +365,7 @@ impl Screen {
|
||||
self.mode_info.clone(),
|
||||
self.colors,
|
||||
self.session_state.clone(),
|
||||
self.draw_pane_frames,
|
||||
);
|
||||
tab.apply_layout(layout, new_pids, tab_index);
|
||||
self.active_tab_index = Some(tab_index);
|
||||
@ -405,6 +412,7 @@ impl Screen {
|
||||
self.mode_info = mode_info;
|
||||
for tab in self.tabs.values_mut() {
|
||||
tab.mode_info = self.mode_info.clone();
|
||||
tab.mark_active_pane_for_rerender();
|
||||
}
|
||||
}
|
||||
pub fn move_focus_left_or_previous_tab(&mut self) {
|
||||
@ -430,6 +438,7 @@ pub(crate) fn screen_thread_main(
|
||||
session_state: Arc<RwLock<SessionState>>,
|
||||
) {
|
||||
let capabilities = config_options.simplified_ui;
|
||||
let draw_pane_frames = !config_options.no_pane_frames;
|
||||
|
||||
let mut screen = Screen::new(
|
||||
bus,
|
||||
@ -443,6 +452,7 @@ pub(crate) fn screen_thread_main(
|
||||
},
|
||||
),
|
||||
session_state,
|
||||
draw_pane_frames,
|
||||
);
|
||||
loop {
|
||||
let (event, mut err_ctx) = screen
|
||||
@ -663,6 +673,13 @@ pub(crate) fn screen_thread_main(
|
||||
.unwrap()
|
||||
.toggle_active_pane_fullscreen();
|
||||
}
|
||||
ScreenInstruction::TogglePaneFrames => {
|
||||
screen.draw_pane_frames = !screen.draw_pane_frames;
|
||||
for (_, tab) in screen.tabs.iter_mut() {
|
||||
tab.set_pane_frames(screen.draw_pane_frames);
|
||||
}
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::NewTab(pane_id) => {
|
||||
screen.new_tab(pane_id);
|
||||
screen
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
||||
use zellij_utils::pane_size::PositionAndSize;
|
||||
use zellij_utils::zellij_tile;
|
||||
|
||||
use crate::tab::Pane;
|
||||
@ -405,73 +406,22 @@ impl Coordinates {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait Rect {
|
||||
fn x(&self) -> usize;
|
||||
fn y(&self) -> usize;
|
||||
fn rows(&self) -> usize;
|
||||
fn columns(&self) -> usize;
|
||||
fn right_boundary_x_coords(&self) -> usize {
|
||||
self.x() + self.columns()
|
||||
}
|
||||
fn bottom_boundary_y_coords(&self) -> usize {
|
||||
self.y() + self.rows()
|
||||
}
|
||||
fn is_directly_right_of(&self, other: &Self) -> bool {
|
||||
self.x() == other.x() + other.columns() + 1
|
||||
}
|
||||
fn is_directly_left_of(&self, other: &Self) -> bool {
|
||||
self.x() + self.columns() + 1 == other.x()
|
||||
}
|
||||
fn is_directly_below(&self, other: &Self) -> bool {
|
||||
self.y() == other.y() + other.rows() + 1
|
||||
}
|
||||
fn is_directly_above(&self, other: &Self) -> bool {
|
||||
self.y() + self.rows() + 1 == other.y()
|
||||
}
|
||||
fn horizontally_overlaps_with(&self, other: &Self) -> bool {
|
||||
(self.y() >= other.y() && self.y() <= (other.y() + other.rows()))
|
||||
|| ((self.y() + self.rows()) <= (other.y() + other.rows())
|
||||
&& (self.y() + self.rows()) > other.y())
|
||||
|| (self.y() <= other.y() && (self.y() + self.rows() >= (other.y() + other.rows())))
|
||||
|| (other.y() <= self.y() && (other.y() + other.rows() >= (self.y() + self.rows())))
|
||||
}
|
||||
fn get_horizontal_overlap_with(&self, other: &Self) -> usize {
|
||||
std::cmp::min(self.y() + self.rows(), other.y() + other.rows())
|
||||
- std::cmp::max(self.y(), other.y())
|
||||
}
|
||||
fn vertically_overlaps_with(&self, other: &Self) -> bool {
|
||||
(self.x() >= other.x() && self.x() <= (other.x() + other.columns()))
|
||||
|| ((self.x() + self.columns()) <= (other.x() + other.columns())
|
||||
&& (self.x() + self.columns()) > other.x())
|
||||
|| (self.x() <= other.x()
|
||||
&& (self.x() + self.columns() >= (other.x() + other.columns())))
|
||||
|| (other.x() <= self.x()
|
||||
&& (other.x() + other.columns() >= (self.x() + self.columns())))
|
||||
}
|
||||
fn get_vertical_overlap_with(&self, other: &Self) -> usize {
|
||||
std::cmp::min(self.x() + self.columns(), other.x() + other.columns())
|
||||
- std::cmp::max(self.x(), other.x())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Boundaries {
|
||||
columns: usize,
|
||||
rows: usize,
|
||||
// boundary_characters: HashMap<Coordinates, BoundaryType>,
|
||||
position_and_size: PositionAndSize,
|
||||
boundary_characters: HashMap<Coordinates, BoundarySymbol>,
|
||||
}
|
||||
|
||||
impl Boundaries {
|
||||
pub fn new(columns: u16, rows: u16) -> Self {
|
||||
let columns = columns as usize;
|
||||
let rows = rows as usize;
|
||||
pub fn new(position_and_size: &PositionAndSize) -> Self {
|
||||
Boundaries {
|
||||
columns,
|
||||
rows,
|
||||
position_and_size: *position_and_size,
|
||||
boundary_characters: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn add_rect(&mut self, rect: &dyn Pane, input_mode: InputMode, palette: Option<Palette>) {
|
||||
if !self.is_fully_inside_screen(rect) {
|
||||
return;
|
||||
}
|
||||
let color = match palette.is_some() {
|
||||
true => match input_mode {
|
||||
InputMode::Normal | InputMode::Locked => Some(palette.unwrap().green),
|
||||
@ -479,19 +429,23 @@ impl Boundaries {
|
||||
},
|
||||
false => None,
|
||||
};
|
||||
if rect.x() > 0 {
|
||||
if rect.x() > self.position_and_size.x {
|
||||
// left boundary
|
||||
let boundary_x_coords = rect.x() - 1;
|
||||
let first_row_coordinates = self.rect_right_boundary_row_start(rect);
|
||||
let last_row_coordinates = self.rect_right_boundary_row_end(rect);
|
||||
for row in first_row_coordinates..last_row_coordinates {
|
||||
let coordinates = Coordinates::new(boundary_x_coords, row);
|
||||
let mut symbol_to_add = if row == first_row_coordinates && row != 0 {
|
||||
BoundarySymbol::new(boundary_type::TOP_LEFT).color(color)
|
||||
} else if row == last_row_coordinates - 1 && row != self.rows - 1 {
|
||||
BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color)
|
||||
} else {
|
||||
BoundarySymbol::new(boundary_type::VERTICAL).color(color)
|
||||
};
|
||||
let mut symbol_to_add =
|
||||
if row == first_row_coordinates && row != self.position_and_size.y {
|
||||
BoundarySymbol::new(boundary_type::TOP_LEFT).color(color)
|
||||
} else if row == last_row_coordinates - 1
|
||||
&& row != self.position_and_size.y + self.position_and_size.rows - 1
|
||||
{
|
||||
BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color)
|
||||
} else {
|
||||
BoundarySymbol::new(boundary_type::VERTICAL).color(color)
|
||||
};
|
||||
if rect.invisible_borders() {
|
||||
symbol_to_add = symbol_to_add.invisible();
|
||||
}
|
||||
@ -503,15 +457,19 @@ impl Boundaries {
|
||||
self.boundary_characters.insert(coordinates, next_symbol);
|
||||
}
|
||||
}
|
||||
if rect.y() > 0 {
|
||||
if rect.y() > self.position_and_size.y {
|
||||
// top boundary
|
||||
let boundary_y_coords = rect.y() - 1;
|
||||
let first_col_coordinates = self.rect_bottom_boundary_col_start(rect);
|
||||
let last_col_coordinates = self.rect_bottom_boundary_col_end(rect);
|
||||
for col in first_col_coordinates..last_col_coordinates {
|
||||
let coordinates = Coordinates::new(col, boundary_y_coords);
|
||||
let mut symbol_to_add = if col == first_col_coordinates && col != 0 {
|
||||
let mut symbol_to_add = if col == first_col_coordinates
|
||||
&& col != self.position_and_size.x
|
||||
{
|
||||
BoundarySymbol::new(boundary_type::TOP_LEFT).color(color)
|
||||
} else if col == last_col_coordinates - 1 && col != self.columns - 1 {
|
||||
} else if col == last_col_coordinates - 1 && col != self.position_and_size.cols - 1
|
||||
{
|
||||
BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color)
|
||||
} else {
|
||||
BoundarySymbol::new(boundary_type::HORIZONTAL).color(color)
|
||||
@ -528,19 +486,22 @@ impl Boundaries {
|
||||
}
|
||||
}
|
||||
if self.rect_right_boundary_is_before_screen_edge(rect) {
|
||||
// let boundary_x_coords = self.rect_right_boundary_x_coords(rect);
|
||||
let boundary_x_coords = rect.right_boundary_x_coords();
|
||||
// right boundary
|
||||
let boundary_x_coords = rect.right_boundary_x_coords() - 1;
|
||||
let first_row_coordinates = self.rect_right_boundary_row_start(rect);
|
||||
let last_row_coordinates = self.rect_right_boundary_row_end(rect);
|
||||
for row in first_row_coordinates..last_row_coordinates {
|
||||
let coordinates = Coordinates::new(boundary_x_coords, row);
|
||||
let mut symbol_to_add = if row == first_row_coordinates && row != 0 {
|
||||
BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color)
|
||||
} else if row == last_row_coordinates - 1 && row != self.rows - 1 {
|
||||
BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color)
|
||||
} else {
|
||||
BoundarySymbol::new(boundary_type::VERTICAL).color(color)
|
||||
};
|
||||
let mut symbol_to_add =
|
||||
if row == first_row_coordinates && row != self.position_and_size.y {
|
||||
BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color)
|
||||
} else if row == last_row_coordinates - 1
|
||||
&& row != self.position_and_size.y + self.position_and_size.rows - 1
|
||||
{
|
||||
BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color)
|
||||
} else {
|
||||
BoundarySymbol::new(boundary_type::VERTICAL).color(color)
|
||||
};
|
||||
if rect.invisible_borders() {
|
||||
symbol_to_add = symbol_to_add.invisible();
|
||||
}
|
||||
@ -553,14 +514,18 @@ impl Boundaries {
|
||||
}
|
||||
}
|
||||
if self.rect_bottom_boundary_is_before_screen_edge(rect) {
|
||||
let boundary_y_coords = rect.bottom_boundary_y_coords();
|
||||
// bottom boundary
|
||||
let boundary_y_coords = rect.bottom_boundary_y_coords() - 1;
|
||||
let first_col_coordinates = self.rect_bottom_boundary_col_start(rect);
|
||||
let last_col_coordinates = self.rect_bottom_boundary_col_end(rect);
|
||||
for col in first_col_coordinates..last_col_coordinates {
|
||||
let coordinates = Coordinates::new(col, boundary_y_coords);
|
||||
let mut symbol_to_add = if col == first_col_coordinates && col != 0 {
|
||||
let mut symbol_to_add = if col == first_col_coordinates
|
||||
&& col != self.position_and_size.x
|
||||
{
|
||||
BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color)
|
||||
} else if col == last_col_coordinates - 1 && col != self.columns - 1 {
|
||||
} else if col == last_col_coordinates - 1 && col != self.position_and_size.cols - 1
|
||||
{
|
||||
BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color)
|
||||
} else {
|
||||
BoundarySymbol::new(boundary_type::HORIZONTAL).color(color)
|
||||
@ -590,27 +555,20 @@ impl Boundaries {
|
||||
vte_output
|
||||
}
|
||||
fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
|
||||
rect.x() + rect.columns() < self.columns
|
||||
rect.x() + rect.columns() < self.position_and_size.cols
|
||||
}
|
||||
fn rect_bottom_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
|
||||
rect.y() + rect.rows() < self.rows
|
||||
rect.y() + rect.rows() < self.position_and_size.y + self.position_and_size.rows
|
||||
}
|
||||
fn rect_right_boundary_row_start(&self, rect: &dyn Pane) -> usize {
|
||||
if rect.y() == 0 {
|
||||
0
|
||||
} else {
|
||||
if rect.y() > self.position_and_size.y {
|
||||
rect.y() - 1
|
||||
} else {
|
||||
self.position_and_size.y
|
||||
}
|
||||
}
|
||||
fn rect_right_boundary_row_end(&self, rect: &dyn Pane) -> usize {
|
||||
let rect_bottom_row = rect.y() + rect.rows();
|
||||
// we do this because unless we're on the screen edge, we'd like to go one extra row to
|
||||
// connect to whatever boundary is beneath us
|
||||
if rect_bottom_row == self.rows {
|
||||
rect_bottom_row
|
||||
} else {
|
||||
rect_bottom_row + 1
|
||||
}
|
||||
rect.y() + rect.rows()
|
||||
}
|
||||
fn rect_bottom_boundary_col_start(&self, rect: &dyn Pane) -> usize {
|
||||
if rect.x() == 0 {
|
||||
@ -620,13 +578,12 @@ impl Boundaries {
|
||||
}
|
||||
}
|
||||
fn rect_bottom_boundary_col_end(&self, rect: &dyn Pane) -> usize {
|
||||
let rect_right_col = rect.x() + rect.columns();
|
||||
// we do this because unless we're on the screen edge, we'd like to go one extra column to
|
||||
// connect to whatever boundary is right of us
|
||||
if rect_right_col == self.columns {
|
||||
rect_right_col
|
||||
} else {
|
||||
rect_right_col + 1
|
||||
}
|
||||
rect.x() + rect.columns()
|
||||
}
|
||||
fn is_fully_inside_screen(&self, rect: &dyn Pane) -> bool {
|
||||
rect.x() >= self.position_and_size.x
|
||||
&& rect.x() + rect.columns() <= self.position_and_size.x + self.position_and_size.cols
|
||||
&& rect.y() >= self.position_and_size.y
|
||||
&& rect.y() + rect.rows() <= self.position_and_size.y + self.position_and_size.rows
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod boundaries;
|
||||
pub mod pane_boundaries_frame;
|
||||
pub mod pane_resizer;
|
||||
pub mod pane_resizer_beta;
|
||||
|
277
zellij-server/src/ui/pane_boundaries_frame.rs
Normal file
277
zellij-server/src/ui/pane_boundaries_frame.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -112,7 +112,7 @@ impl<'a> PaneResizer<'a> {
|
||||
(pane.x(), pane.y(), pane.columns(), pane.rows())
|
||||
};
|
||||
let panes_to_pull = self.panes.values_mut().filter(|p| {
|
||||
p.x() > pane_x + pane_columns
|
||||
p.x() >= pane_x + pane_columns
|
||||
&& (p.y() <= pane_y && p.y() + p.rows() >= pane_y
|
||||
|| p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows)
|
||||
});
|
||||
@ -137,7 +137,7 @@ impl<'a> PaneResizer<'a> {
|
||||
(pane.x(), pane.y(), pane.columns(), pane.rows())
|
||||
};
|
||||
let panes_to_pull = self.panes.values_mut().filter(|p| {
|
||||
p.y() > pane_y + pane_rows
|
||||
p.y() >= pane_y + pane_rows
|
||||
&& (p.x() <= pane_x && p.x() + p.columns() >= pane_x
|
||||
|| p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns)
|
||||
});
|
||||
@ -162,7 +162,7 @@ impl<'a> PaneResizer<'a> {
|
||||
(pane.x(), pane.y(), pane.columns(), pane.rows())
|
||||
};
|
||||
let panes_to_push = self.panes.values_mut().filter(|p| {
|
||||
p.y() > pane_y + pane_rows
|
||||
p.y() >= pane_y + pane_rows
|
||||
&& (p.x() <= pane_x && p.x() + p.columns() >= pane_x
|
||||
|| p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns)
|
||||
});
|
||||
@ -187,7 +187,7 @@ impl<'a> PaneResizer<'a> {
|
||||
(pane.x(), pane.y(), pane.columns(), pane.rows())
|
||||
};
|
||||
let panes_to_push = self.panes.values_mut().filter(|p| {
|
||||
p.x() > pane_x + pane_columns
|
||||
p.x() >= pane_x + pane_columns
|
||||
&& (p.y() <= pane_y && p.y() + p.rows() >= pane_y
|
||||
|| p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows)
|
||||
});
|
||||
@ -204,32 +204,44 @@ impl<'a> PaneResizer<'a> {
|
||||
let pane = self.panes.get_mut(id).unwrap();
|
||||
pane.reduce_height_up(count);
|
||||
if let PaneId::Terminal(pid) = id {
|
||||
self.os_api
|
||||
.set_terminal_size_using_fd(*pid, pane.columns() as u16, pane.rows() as u16);
|
||||
self.os_api.set_terminal_size_using_fd(
|
||||
*pid,
|
||||
pane.get_content_columns() as u16,
|
||||
pane.get_content_rows() as u16,
|
||||
);
|
||||
}
|
||||
}
|
||||
fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) {
|
||||
let pane = self.panes.get_mut(id).unwrap();
|
||||
pane.increase_height_down(count);
|
||||
if let PaneId::Terminal(pid) = pane.pid() {
|
||||
self.os_api
|
||||
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16);
|
||||
self.os_api.set_terminal_size_using_fd(
|
||||
pid,
|
||||
pane.get_content_columns() as u16,
|
||||
pane.get_content_rows() as u16,
|
||||
);
|
||||
}
|
||||
}
|
||||
fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) {
|
||||
let pane = self.panes.get_mut(id).unwrap();
|
||||
pane.increase_width_right(count);
|
||||
if let PaneId::Terminal(pid) = pane.pid() {
|
||||
self.os_api
|
||||
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16);
|
||||
self.os_api.set_terminal_size_using_fd(
|
||||
pid,
|
||||
pane.get_content_columns() as u16,
|
||||
pane.get_content_rows() as u16,
|
||||
);
|
||||
}
|
||||
}
|
||||
fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) {
|
||||
let pane = self.panes.get_mut(id).unwrap();
|
||||
pane.reduce_width_left(count);
|
||||
if let PaneId::Terminal(pid) = pane.pid() {
|
||||
self.os_api
|
||||
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16);
|
||||
self.os_api.set_terminal_size_using_fd(
|
||||
pid,
|
||||
pane.get_content_columns() as u16,
|
||||
pane.get_content_rows() as u16,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -240,9 +252,7 @@ fn find_next_increasable_horizontal_pane(
|
||||
increase_by: usize,
|
||||
) -> Option<PaneId> {
|
||||
let next_pane_candidates = panes.values().filter(
|
||||
|p| {
|
||||
p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of)
|
||||
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
|
||||
|p| p.x() == right_of.x() + right_of.columns() && p.horizontally_overlaps_with(right_of), // TODO: the name here is wrong, it should be vertically_overlaps_with
|
||||
);
|
||||
let resizable_candidates =
|
||||
next_pane_candidates.filter(|p| p.can_increase_height_by(increase_by));
|
||||
@ -265,7 +275,7 @@ fn find_next_increasable_vertical_pane(
|
||||
increase_by: usize,
|
||||
) -> Option<PaneId> {
|
||||
let next_pane_candidates = panes.values().filter(
|
||||
|p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
|
||||
|p| p.y() == below.y() + below.rows() && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
|
||||
);
|
||||
let resizable_candidates =
|
||||
next_pane_candidates.filter(|p| p.can_increase_width_by(increase_by));
|
||||
@ -288,7 +298,7 @@ fn find_next_reducible_vertical_pane(
|
||||
reduce_by: usize,
|
||||
) -> Option<PaneId> {
|
||||
let next_pane_candidates = panes.values().filter(
|
||||
|p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
|
||||
|p| p.y() == below.y() + below.rows() && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
|
||||
);
|
||||
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_width_by(reduce_by));
|
||||
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
|
||||
@ -310,9 +320,7 @@ fn find_next_reducible_horizontal_pane(
|
||||
reduce_by: usize,
|
||||
) -> Option<PaneId> {
|
||||
let next_pane_candidates = panes.values().filter(
|
||||
|p| {
|
||||
p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of)
|
||||
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
|
||||
|p| p.x() == right_of.x() + right_of.columns() && p.horizontally_overlaps_with(right_of), // TODO: the name here is wrong, it should be vertically_overlaps_with
|
||||
);
|
||||
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_height_by(reduce_by));
|
||||
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
|
||||
@ -346,7 +354,7 @@ fn find_increasable_horizontal_chain(
|
||||
{
|
||||
Some(leftmost_pane) => {
|
||||
if !leftmost_pane.can_increase_height_by(increase_by) {
|
||||
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
|
||||
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
|
||||
continue;
|
||||
}
|
||||
let mut panes_to_resize = vec![];
|
||||
@ -365,7 +373,7 @@ fn find_increasable_horizontal_chain(
|
||||
current_pane = panes.get(&next_pane_id).unwrap();
|
||||
}
|
||||
None => {
|
||||
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
|
||||
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
|
||||
break;
|
||||
}
|
||||
};
|
||||
@ -396,7 +404,7 @@ fn find_increasable_vertical_chain(
|
||||
{
|
||||
Some(topmost_pane) => {
|
||||
if !topmost_pane.can_increase_width_by(increase_by) {
|
||||
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
|
||||
vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
|
||||
continue;
|
||||
}
|
||||
let mut panes_to_resize = vec![];
|
||||
@ -415,7 +423,7 @@ fn find_increasable_vertical_chain(
|
||||
current_pane = panes.get(&next_pane_id).unwrap();
|
||||
}
|
||||
None => {
|
||||
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
|
||||
vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
|
||||
break;
|
||||
}
|
||||
};
|
||||
@ -446,7 +454,7 @@ fn find_reducible_horizontal_chain(
|
||||
{
|
||||
Some(leftmost_pane) => {
|
||||
if !leftmost_pane.can_reduce_height_by(reduce_by) {
|
||||
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
|
||||
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
|
||||
continue;
|
||||
}
|
||||
let mut panes_to_resize = vec![];
|
||||
@ -465,7 +473,7 @@ fn find_reducible_horizontal_chain(
|
||||
current_pane = panes.get(&next_pane_id).unwrap();
|
||||
}
|
||||
None => {
|
||||
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
|
||||
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
|
||||
break;
|
||||
}
|
||||
};
|
||||
@ -496,7 +504,7 @@ fn find_reducible_vertical_chain(
|
||||
{
|
||||
Some(topmost_pane) => {
|
||||
if !topmost_pane.can_reduce_width_by(increase_by) {
|
||||
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
|
||||
vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
|
||||
continue;
|
||||
}
|
||||
let mut panes_to_resize = vec![];
|
||||
@ -515,7 +523,7 @@ fn find_reducible_vertical_chain(
|
||||
current_pane = panes.get(&next_pane_id).unwrap();
|
||||
}
|
||||
None => {
|
||||
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
|
||||
vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
0
zellij-server/src/ui/title_telescope.rs
Normal file
0
zellij-server/src/ui/title_telescope.rs
Normal file
@ -82,7 +82,14 @@ fn create_new_screen(position_and_size: PositionAndSize) -> Screen {
|
||||
let max_panes = None;
|
||||
let mode_info = ModeInfo::default();
|
||||
let session_state = Arc::new(RwLock::new(SessionState::Attached));
|
||||
Screen::new(bus, &client_attributes, max_panes, mode_info, session_state)
|
||||
Screen::new(
|
||||
bus,
|
||||
&client_attributes,
|
||||
max_panes,
|
||||
mode_info,
|
||||
session_state,
|
||||
false, // draw_pane_frames
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -133,15 +133,19 @@ pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_d
|
||||
drop(bus.senders.send_to_screen(ScreenInstruction::Render));
|
||||
}
|
||||
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
if rows == 0 || cols == 0 {
|
||||
buf_tx.send(String::new()).unwrap();
|
||||
} else {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
|
||||
let render = instance.exports.get_function("render").unwrap();
|
||||
let render = instance.exports.get_function("render").unwrap();
|
||||
|
||||
render
|
||||
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
|
||||
.unwrap();
|
||||
render
|
||||
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
|
||||
.unwrap();
|
||||
|
||||
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::Exit => break,
|
||||
|
@ -105,6 +105,8 @@ keybinds:
|
||||
key: [Char: 'x',]
|
||||
- action: [ToggleFocusFullscreen,]
|
||||
key: [Char: 'f',]
|
||||
- action: [TogglePaneFrames,]
|
||||
key: [Char: 'z',]
|
||||
- action: [FocusPreviousPane,]
|
||||
key: [ Alt: '[',]
|
||||
- action: [FocusNextPane,]
|
||||
|
@ -2,12 +2,14 @@
|
||||
direction: Horizontal
|
||||
parts:
|
||||
- direction: Vertical
|
||||
borderless: true
|
||||
split_size:
|
||||
Fixed: 1
|
||||
run:
|
||||
plugin: tab-bar
|
||||
- direction: Vertical
|
||||
- direction: Vertical
|
||||
borderless: true
|
||||
split_size:
|
||||
Fixed: 2
|
||||
run:
|
||||
|
@ -2,6 +2,7 @@
|
||||
direction: Horizontal
|
||||
parts:
|
||||
- direction: Vertical
|
||||
borderless: true
|
||||
split_size:
|
||||
Fixed: 1
|
||||
run:
|
||||
|
@ -2,6 +2,7 @@
|
||||
direction: Horizontal
|
||||
parts:
|
||||
- direction: Vertical
|
||||
borderless: true
|
||||
split_size:
|
||||
Fixed: 1
|
||||
run:
|
||||
@ -15,6 +16,7 @@ parts:
|
||||
plugin: strider
|
||||
- direction: Horizontal
|
||||
- direction: Vertical
|
||||
borderless: true
|
||||
split_size:
|
||||
Fixed: 2
|
||||
run:
|
||||
|
@ -209,6 +209,7 @@ pub enum ScreenContext {
|
||||
CloseFocusedPane,
|
||||
ToggleActiveSyncTab,
|
||||
ToggleActiveTerminalFullscreen,
|
||||
TogglePaneFrames,
|
||||
SetSelectable,
|
||||
SetInvisibleBorders,
|
||||
SetFixedHeight,
|
||||
|
@ -56,6 +56,8 @@ pub enum Action {
|
||||
PageScrollDown,
|
||||
/// Toggle between fullscreen focus pane and normal layout.
|
||||
ToggleFocusFullscreen,
|
||||
/// Toggle frames around panes in the UI
|
||||
TogglePaneFrames,
|
||||
/// Toggle between sending text commands to all panes on the current tab and normal mode.
|
||||
ToggleActiveSyncTab,
|
||||
/// Open a new pane in the specified direction (relative to focus).
|
||||
|
@ -50,6 +50,8 @@ pub struct Layout {
|
||||
pub parts: Vec<Layout>,
|
||||
pub split_size: Option<SplitSize>,
|
||||
pub run: Option<Run>,
|
||||
#[serde(default)]
|
||||
pub borderless: bool,
|
||||
}
|
||||
|
||||
type LayoutResult = Result<Layout, ConfigError>;
|
||||
@ -141,6 +143,14 @@ impl Layout {
|
||||
total_panes
|
||||
}
|
||||
|
||||
pub fn total_borderless_panes(&self) -> usize {
|
||||
let mut total_borderless_panes = 0;
|
||||
total_borderless_panes += self.parts.iter().filter(|p| p.borderless).count();
|
||||
for part in self.parts.iter() {
|
||||
total_borderless_panes += part.total_borderless_panes();
|
||||
}
|
||||
total_borderless_panes
|
||||
}
|
||||
pub fn extract_run_instructions(&self) -> Vec<Option<Run>> {
|
||||
let mut run_instructions = vec![];
|
||||
if self.parts.is_empty() {
|
||||
@ -168,7 +178,7 @@ fn split_space_to_parts_vertically(
|
||||
let mut split_parts = Vec::new();
|
||||
let mut current_x_position = space_to_split.x;
|
||||
let mut current_width = 0;
|
||||
let max_width = space_to_split.cols - (sizes.len() - 1); // minus space for gaps
|
||||
let max_width = space_to_split.cols;
|
||||
|
||||
let mut parts_to_grow = Vec::new();
|
||||
|
||||
@ -192,7 +202,7 @@ fn split_space_to_parts_vertically(
|
||||
..Default::default()
|
||||
});
|
||||
current_width += columns;
|
||||
current_x_position += columns + 1; // 1 for gap
|
||||
current_x_position += columns;
|
||||
}
|
||||
|
||||
if current_width > max_width {
|
||||
@ -210,7 +220,7 @@ fn split_space_to_parts_vertically(
|
||||
last_flexible_index = idx;
|
||||
}
|
||||
current_width += part.cols;
|
||||
current_x_position += part.cols + 1; // 1 for gap
|
||||
current_x_position += part.cols;
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,7 +243,7 @@ fn split_space_to_parts_horizontally(
|
||||
let mut split_parts = Vec::new();
|
||||
let mut current_y_position = space_to_split.y;
|
||||
let mut current_height = 0;
|
||||
let max_height = space_to_split.rows - (sizes.len() - 1); // minus space for gaps
|
||||
let max_height = space_to_split.rows;
|
||||
|
||||
let mut parts_to_grow = Vec::new();
|
||||
|
||||
@ -256,7 +266,7 @@ fn split_space_to_parts_horizontally(
|
||||
..Default::default()
|
||||
});
|
||||
current_height += rows;
|
||||
current_y_position += rows + 1; // 1 for gap
|
||||
current_y_position += rows;
|
||||
}
|
||||
|
||||
if current_height > max_height {
|
||||
@ -275,7 +285,7 @@ fn split_space_to_parts_horizontally(
|
||||
last_flexible_index = idx;
|
||||
}
|
||||
current_height += part.rows;
|
||||
current_y_position += part.rows + 1; // 1 for gap
|
||||
current_y_position += part.rows;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ pub fn get_mode_info(
|
||||
("r".to_string(), "Right split".to_string()),
|
||||
("x".to_string(), "Close".to_string()),
|
||||
("f".to_string(), "Fullscreen".to_string()),
|
||||
("z".to_string(), "Frames".to_string()),
|
||||
],
|
||||
InputMode::Tab => vec![
|
||||
("←↓↑→".to_string(), "Move focus".to_string()),
|
||||
|
@ -58,6 +58,9 @@ pub struct Options {
|
||||
#[serde(default)]
|
||||
/// Disable handling of mouse events
|
||||
pub disable_mouse_mode: bool,
|
||||
#[structopt(long)]
|
||||
#[serde(default)]
|
||||
pub no_pane_frames: bool,
|
||||
/// Set behaviour on force close (quit or detach)
|
||||
#[structopt(long)]
|
||||
pub on_force_close: Option<OnForceClose>,
|
||||
@ -80,6 +83,7 @@ impl Options {
|
||||
|
||||
let simplified_ui = merge_bool(other.simplified_ui, self.simplified_ui);
|
||||
let disable_mouse_mode = merge_bool(other.disable_mouse_mode, self.disable_mouse_mode);
|
||||
let no_pane_frames = merge_bool(other.no_pane_frames, self.no_pane_frames);
|
||||
|
||||
let default_mode = other.default_mode.or(self.default_mode);
|
||||
let default_shell = other.default_shell.or_else(|| self.default_shell.clone());
|
||||
@ -94,6 +98,7 @@ impl Options {
|
||||
default_shell,
|
||||
layout_dir,
|
||||
disable_mouse_mode,
|
||||
no_pane_frames,
|
||||
on_force_close,
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::position::Position;
|
||||
|
||||
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
|
||||
/// in character rows and columns.
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PositionAndSize {
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
@ -34,4 +34,16 @@ impl PositionAndSize {
|
||||
let row = point.line.0 as usize;
|
||||
self.x <= col && col < self.x + self.cols && self.y <= row && row < self.y + self.rows
|
||||
}
|
||||
pub fn reduce_outer_frame(mut self, frame_width: usize) -> Self {
|
||||
self.x += frame_width;
|
||||
self.rows -= frame_width * 2;
|
||||
self.y += frame_width;
|
||||
self.cols -= frame_width * 2;
|
||||
self
|
||||
}
|
||||
pub fn reduce_top_line(mut self) -> Self {
|
||||
self.y += 1;
|
||||
self.rows -= 1;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ pub fn set_permissions(path: &Path) -> io::Result<()> {
|
||||
fs::set_permissions(path, permissions)
|
||||
}
|
||||
|
||||
fn ansi_len(s: &str) -> usize {
|
||||
pub fn ansi_len(s: &str) -> usize {
|
||||
from_utf8(&strip(s.as_bytes()).unwrap())
|
||||
.unwrap()
|
||||
.chars()
|
||||
|
Loading…
Reference in New Issue
Block a user