diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f68875bc..559d9968bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,13 @@ 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) +* Terminal compatibility: support changing index colors through OSC 4 and similar (https://github.com/zellij-org/zellij/pull/646) +* Fix various shells (eg. nushell) unexpectedly exiting when the user presses ctrl-c (https://github.com/zellij-org/zellij/pull/648) +* Fix line wrapping while scrolling (https://github.com/zellij-org/zellij/pull/650) +* Indicate to the user when text is copied to the clipboard with the mouse (https://github.com/zellij-org/zellij/pull/642) +* Terminal compatibility: fix progress bar line overflow (http://github.com/zellij-org/zellij/pull/656) +* Add action to toggle between tabs `ToggleTab`, bound by default to [TAB] in tab mode (https://github.com/zellij-org/zellij/pull/622) ## [0.15.0] - 2021-07-19 * Kill children properly (https://github.com/zellij-org/zellij/pull/601) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 6986306978..1c2c3f5726 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -12,12 +12,24 @@ In case of ties, one member designated as Arbiter can break the tie. For reasons Some decisions that should always be brought to the quorum of members: * Adding new members to the organization +* Removing existing members from the organization * Any decision involving money or finances * Collaborations with outside organizations ## Role of the Arbiter The Arbiter is a member of the quorum who serves as a tie breaker in votes and also has veto privileges for the votes themselves. Both of these should be used as seldom as possible and preferably not at all. -Once the organization reaches 10 members, a reasonable and achievable process must be established in order to replace the Arbiter if the need arises (for example: a majority vote of members can replace the Arbiter with another candidate). In any case, the Arbiter has to be an organization member. + +### Arbiter no-confidence vote +It is preferable that any conflict with the Arbiter, no matter how severe, be resolved amicably. However, it is acknowledged that this is not always possible. For those cases, and for the health of Zellij and its community, this is the process in order to remove a sitting Arbiter against their will: + +1. Find another willing candidate from the existing organization members +2. Email all the organization members to the addresses specified in this document, letting them know about the vote and the replacement candidate +3. All the maintainers have 2 weeks to reply (privately) with a yes (remove the Arbiter) or no (do not remove the Arbiter) vote - the sitting Arbiter does not get a vote +4. The votes should be tallied and verified by the vote initiator as well as another organization member who is not the sitting Arbiter +5. If at least two thirds (rounded up) of the respondents vote to remove - the vote succeeds and the new Arbiter is instated in place of the sitting one +6. The sitting Arbiter remains an organization member unless explicitly removed by a normal majority vote + +For the removal of doubt: changing this rule itself requires a two thirds majority of maintainers as well. ## Current Organization Members * Aram Drevekenin (Arbiter) @@ -29,4 +41,4 @@ Once the organization reaches 10 members, a reasonable and achievable process mu * Roee Shapira * Alex Kenji Berthold * Kyle Sutherland-Cash -* Dante Pippi \ No newline at end of file +* Dante Pippi diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 83eb50c9ae..d2c6d82b54 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -8,7 +8,7 @@ use zellij_tile::prelude::*; use zellij_tile_utils::style; use first_line::{ctrl_keys, superkey}; -use second_line::keybinds; +use second_line::{keybinds, text_copied_hint}; // for more of these, copy paste from: https://en.wikipedia.org/wiki/Box-drawing_character static ARROW_SEPARATOR: &str = ""; @@ -17,6 +17,7 @@ static MORE_MSG: &str = " ... "; #[derive(Default)] struct State { mode_info: ModeInfo, + diplay_text_copied_hint: bool, } register_plugin!(State); @@ -136,12 +137,25 @@ impl ZellijPlugin for State { set_selectable(false); set_invisible_borders(true); set_fixed_height(2); - subscribe(&[EventType::ModeUpdate]); + subscribe(&[ + EventType::ModeUpdate, + EventType::CopyToClipboard, + EventType::InputReceived, + ]); } fn update(&mut self, event: Event) { - if let Event::ModeUpdate(mode_info) = event { - self.mode_info = mode_info; + match event { + Event::ModeUpdate(mode_info) => { + self.mode_info = mode_info; + } + Event::CopyToClipboard => { + self.diplay_text_copied_hint = true; + } + Event::InputReceived => { + self.diplay_text_copied_hint = false; + } + _ => {} } } @@ -154,10 +168,18 @@ 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); + let second_line = if self.diplay_text_copied_hint { + text_copied_hint(&self.mode_info.palette) + } else { + keybinds(&self.mode_info, cols) + }; // [48;5;238m is gray background, [0K is so that it fills the rest of the line // [m is background reset, [0K is so that it clears the rest of the line diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 750df77b64..d73cb28745 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -375,3 +375,15 @@ pub fn keybinds(help: &ModeInfo, max_width: usize) -> LinePart { } best_effort_shortcut_list(help, max_width) } + +pub fn text_copied_hint(palette: &Palette) -> LinePart { + let hint = " Text copied to clipboard"; + let green_color = match palette.green { + PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), + PaletteColor::EightBit(color) => Fixed(color), + }; + LinePart { + part: format!("{}", Style::new().fg(green_color).bold().paint(hint)), + len: hint.len(), + } +} diff --git a/default-plugins/strider/src/state.rs b/default-plugins/strider/src/state.rs index 0d96ae0b98..0ae84ab295 100644 --- a/default-plugins/strider/src/state.rs +++ b/default-plugins/strider/src/state.rs @@ -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() diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs index 3ebeaac312..fc18abfd1f 100644 --- a/default-plugins/tab-bar/src/line.rs +++ b/default-plugins/tab-bar/src/line.rs @@ -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 = 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), ); diff --git a/example/layouts/multiple_tabs_layout.yaml b/example/layouts/multiple_tabs_layout.yaml new file mode 100644 index 0000000000..cba76b88d0 --- /dev/null +++ b/example/layouts/multiple_tabs_layout.yaml @@ -0,0 +1,87 @@ +--- +template: + direction: Horizontal + parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + borderless: true + - direction: Vertical + body: true + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar + borderless: true + +tabs: +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Horizontal + split_size: + Percent: 50 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical +- direction: Vertical +- direction: Vertical +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 20 + run: + plugin: strider + - direction: Horizontal + split_size: + Percent: 80 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 40 + - direction: Horizontal + split_size: + Percent: 60 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 diff --git a/example/layouts/multiple_tabs_layout_htop_command.yaml b/example/layouts/multiple_tabs_layout_htop_command.yaml new file mode 100644 index 0000000000..13901c1309 --- /dev/null +++ b/example/layouts/multiple_tabs_layout_htop_command.yaml @@ -0,0 +1,90 @@ +--- +template: + direction: Horizontal + parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + borderless: true + - direction: Vertical + body: true + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar + borderless: true + +tabs: +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 50 + run: + command: {cmd: htop} + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Horizontal + split_size: + Percent: 50 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical +- direction: Vertical + run: + command: {cmd: htop, args: ["-C"]} +- direction: Vertical +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 20 + run: + plugin: strider + - direction: Horizontal + split_size: + Percent: 80 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 40 + - direction: Horizontal + split_size: + Percent: 60 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 diff --git a/example/layouts/run_htop_layout.yaml b/example/layouts/run_htop_layout.yaml new file mode 100644 index 0000000000..91f4f63cbe --- /dev/null +++ b/example/layouts/run_htop_layout.yaml @@ -0,0 +1,21 @@ +--- +tabs: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 50 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 + run: + command: {cmd: htop} + - direction: Horizontal + split_size: + Percent: 50 + run: + command: {cmd: htop} diff --git a/example/layouts/run_htop_layout_with_plugins.yaml b/example/layouts/run_htop_layout_with_plugins.yaml new file mode 100644 index 0000000000..2868cba520 --- /dev/null +++ b/example/layouts/run_htop_layout_with_plugins.yaml @@ -0,0 +1,33 @@ +--- +tabs: + - direction: Horizontal + parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + borderless: true + - direction: Vertical + parts: + - direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 50 + run: + command: {cmd: htop} + - direction: Vertical + split_size: + Percent: 50 + run: + command: {cmd: htop, args: ["-C"]} + - direction: Vertical + split_size: + Fixed: 5 + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar + borderless: true diff --git a/example/run_htop_layout.yaml b/example/run_htop_layout.yaml deleted file mode 100644 index 949435d694..0000000000 --- a/example/run_htop_layout.yaml +++ /dev/null @@ -1,20 +0,0 @@ ---- -direction: Vertical -parts: - - direction: Horizontal - split_size: - Percent: 50 - parts: - - direction: Vertical - split_size: - Percent: 50 - - direction: Vertical - split_size: - Percent: 50 - run: - command: {cmd: htop} - - direction: Horizontal - split_size: - Percent: 50 - run: - command: {cmd: htop} diff --git a/example/run_htop_layout_with_plugins.yaml b/example/run_htop_layout_with_plugins.yaml deleted file mode 100644 index 99f72edbe6..0000000000 --- a/example/run_htop_layout_with_plugins.yaml +++ /dev/null @@ -1,30 +0,0 @@ ---- -direction: Horizontal -parts: - - direction: Vertical - split_size: - Fixed: 1 - run: - plugin: tab-bar - - direction: Vertical - parts: - - direction: Vertical - parts: - - direction: Vertical - split_size: - Percent: 50 - run: - command: {cmd: htop} - - direction: Vertical - split_size: - Percent: 50 - run: - command: {cmd: htop, args: ["-C"]} - - direction: Vertical - split_size: - Fixed: 5 - - direction: Vertical - split_size: - Fixed: 2 - run: - plugin: status-bar diff --git a/src/main.rs b/src/main.rs index 1a3a16ed67..f0d00f77ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,14 +24,6 @@ pub fn main() { list_sessions(); } - let (config, layout, config_options) = match Setup::from_options(&opts) { - Ok(results) => results, - Err(e) => { - eprintln!("{}", e); - process::exit(1); - } - }; - atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap(); atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap(); if let Some(path) = opts.server { @@ -62,6 +54,14 @@ pub fn main() { session_name = Some(get_active_session()); } + let (config, _, config_options) = match Setup::from_options(&opts) { + Ok(results) => results, + Err(e) => { + eprintln!("{}", e); + process::exit(1); + } + }; + start_client( Box::new(os_input), opts, @@ -70,6 +70,14 @@ pub fn main() { None, ); } else { + let (config, layout, _) = match Setup::from_options(&opts) { + Ok(results) => results, + Err(e) => { + eprintln!("{}", e); + process::exit(1); + } + }; + let session_name = opts .session .clone() diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index 8d41d44f94..d28ae2f76b 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -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); +} diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index 053df477ad..819bee8660 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -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,8 +143,7 @@ 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("─────") - // this is a bug that happens because the app draws borders around the status bar momentarily on first render + self.current_snapshot.contains("Ctrl +") } pub fn snapshot_contains(&self, text: &str) -> bool { self.current_snapshot.contains(text) @@ -198,6 +204,7 @@ pub struct RemoteRunner { retries_left: usize, win_size: PositionAndSize, layout_file_name: Option<&'static str>, + without_frames: bool, } impl RemoteRunner { @@ -209,7 +216,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 +231,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, + ) -> 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 +266,11 @@ impl RemoteRunner { layout_file_name: &'static str, session_name: Option, ) -> 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 +289,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 +349,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; diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__accepts_basic_layout.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__accepts_basic_layout.snap index 5556153a5a..02ded6de50 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__accepts_basic_layout.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__accepts_basic_layout.snap @@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs expression: last_snapshot --- -$ █ │$ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ -───────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────── -$ - - - - +┌ Pane #1 ─────────────┐┌ Pane #2 ─────────────────────────────────────────────────────────────────────────────────────┐ +│$ █ ││$ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +└──────────────────────┘└──────────────────────────────────────────────────────────────────────────────────────────────┘ +┌ Pane #3 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│$ │ +│ │ +│ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap index 23ffc097de..91c83a4432 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap @@ -1,10 +1,10 @@ --- -source: src/tests/integration/e2e.rs +source: src/tests/e2e/cases.rs expression: last_snapshot --- - +──────── $ Hi!█ diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap index 32f6d26f2d..e748702838 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap @@ -1,10 +1,10 @@ --- -source: src/tests/integration/e2e.rs +source: src/tests/e2e/cases.rs expression: last_snapshot --- Zellij  Tab #1  - +─ Pane #1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────── $ █ diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap index 983331aa2e..92484dd681 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap @@ -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 + LOCK 

PANE  TAB  RESIZE  SCROLL  SESSION  QUIT  Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap index 79e84810da..5a40976062 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap @@ -4,26 +4,26 @@ expression: last_snapshot --- Zellij  Tab #1  - -$ █ │$ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - │ - +┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐ +│$ █ ││$ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  SCROLL  SESSION  QUIT  Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap index bc54e6753c..0796484c6d 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap @@ -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█ diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap index 9b8267835a..f9b84f68d1 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap @@ -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 ────────────────────────────────────────────────────────────────────────────────────────────────────────────── $ █ diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap index 5f1ed7f64f..ae130ab5be 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap @@ -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 + LOCK 

PANE  TAB  RESIZE  SCROLL  SESSION  QUIT  Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap index a57d4849a9..b329e8c6d7 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap @@ -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 + LOCK 

PANE  TAB  RESIZE  SCROLL  SESSION  QUIT  Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap index a609be4f2e..a33ab400c3 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap @@ -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 + LOCK 

PANE  TAB  RESIZE  SCROLL  SESSION  QUIT  <↓↑> Scroll / Scroll Page / Select pane diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap index bff4037641..dfed0611da 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap @@ -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 + LOCK 

PANE  TAB  RESIZE  SCROLL  SESSION  QUIT  Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap index bbb5b068ac..36f0e6d89b 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap @@ -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 + LOCK 

PANE  TAB  RESIZE  SCROLL  SESSION  QUIT  Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap new file mode 100644 index 0000000000..85c6db5461 --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap @@ -0,0 +1,29 @@ +--- +source: src/tests/e2e/cases.rs +expression: last_snapshot + +--- + Zellij  Tab #1  +$ │$ █ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + Ctrl + LOCK 

PANE  TAB  RESIZE  SCROLL  SESSION  QUIT  + Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap index 32f6d26f2d..e748702838 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap @@ -1,10 +1,10 @@ --- -source: src/tests/integration/e2e.rs +source: src/tests/e2e/cases.rs expression: last_snapshot --- Zellij  Tab #1  - +─ Pane #1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────── $ █ diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap index 98ddffc76f..9108099d53 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap @@ -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 + LOCK 

PANE  TAB  RESIZE  SCROLL  SESSION  QUIT  + Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes. diff --git a/src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml b/src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml index e1a1a6071e..aac8c4575d 100644 --- a/src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml +++ b/src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml @@ -1,5 +1,6 @@ --- - direction: Horizontal +tabs: + - direction: Horizontal parts: - direction: Vertical parts: @@ -9,6 +10,8 @@ - direction: Horizontal split_size: Percent: 50 + tabs: + - direction: Horizontal split_size: Percent: 80 - direction: Vertical diff --git a/src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml b/src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml index 33d942253b..8f3fcdbd35 100644 --- a/src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml +++ b/src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml @@ -1,5 +1,6 @@ --- - direction: Horizontal +tabs: + - direction: Horizontal parts: - direction: Vertical parts: @@ -9,6 +10,9 @@ - direction: Horizontal split_size: Percent: 90 + - direction: Horizontal + tabs: + - direction: Horizontal split_size: Percent: 80 - direction: Vertical diff --git a/src/tests/fixtures/layouts/three-panes-with-nesting.yaml b/src/tests/fixtures/layouts/three-panes-with-nesting.yaml index f1e0dd7e5c..2d0aed95d8 100644 --- a/src/tests/fixtures/layouts/three-panes-with-nesting.yaml +++ b/src/tests/fixtures/layouts/three-panes-with-nesting.yaml @@ -1,16 +1,17 @@ --- -direction: Horizontal -parts: - - direction: Vertical +tabs: + - direction: Horizontal parts: - - direction: Horizontal - split_size: - Percent: 20 - - direction: Horizontal + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 20 + - direction: Horizontal + split_size: + Percent: 80 split_size: Percent: 80 - split_size: - Percent: 80 - - direction: Vertical - split_size: - Percent: 20 + - direction: Vertical + split_size: + Percent: 20 diff --git a/src/tests/fixtures/scrolling b/src/tests/fixtures/scrolling new file mode 100644 index 0000000000..89d9bbd768 --- /dev/null +++ b/src/tests/fixtures/scrolling @@ -0,0 +1,20 @@ + line 1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 5aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 10aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 11aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 12aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 13aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 14aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 16aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 17aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 18aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 19aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + line 20aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index f02b5ad818..217b10f975 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -91,8 +91,10 @@ impl InputHandler { self.handle_key(&key, raw_bytes); } else if unsupported_key == bracketed_paste_start { self.pasting = true; + self.handle_unknown_key(raw_bytes); } else if unsupported_key == bracketed_paste_end { self.pasting = false; + self.handle_unknown_key(raw_bytes); } else { // this is a hack because termion doesn't recognize certain keys // in this case we just forward it to the terminal @@ -183,11 +185,12 @@ impl InputHandler { } Action::CloseFocus | Action::NewPane(_) - | Action::NewTab + | Action::NewTab(_) | Action::GoToNextTab | Action::GoToPreviousTab | Action::CloseTab | Action::GoToTab(_) + | Action::ToggleTab | Action::MoveFocusOrTab(_) => { self.command_is_executing.blocking_input_thread(); self.os_input diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 5c832e8607..e806ec3bf0 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -14,14 +14,14 @@ use crate::{ command_is_executing::CommandIsExecuting, input_handler::input_loop, os_input_output::ClientOsApi, }; -use zellij_utils::cli::CliArgs; use zellij_utils::{ channels::{self, ChannelWithContext, SenderWithContext}, consts::{SESSION_NAME, ZELLIJ_IPC_PIPE}, errors::{ClientContext, ContextType, ErrorInstruction}, - input::{actions::Action, config::Config, layout::Layout, options::Options}, + input::{actions::Action, config::Config, options::Options}, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, }; +use zellij_utils::{cli::CliArgs, input::layout::LayoutFromYaml}; /// Instructions related to the client-side application #[derive(Debug, Clone)] @@ -87,7 +87,7 @@ pub fn start_client( opts: CliArgs, config: Config, info: ClientInfo, - layout: Option, + layout: Option, ) { info!("Starting Zellij client!"); let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l"; diff --git a/zellij-client/src/unit/input_handler_tests.rs b/zellij-client/src/unit/input_handler_tests.rs index f40344fe45..9b0bc71c13 100644 --- a/zellij-client/src/unit/input_handler_tests.rs +++ b/zellij-client/src/unit/input_handler_tests.rs @@ -259,7 +259,9 @@ pub fn bracketed_paste() { default_mode, )); let expected_actions_sent_to_server = vec![ + Action::Write(commands::BRACKETED_PASTE_START.to_vec()), Action::Write(commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec()), // keys were directly written to server and not interpreted + Action::Write(commands::BRACKETED_PASTE_END.to_vec()), Action::Quit, ]; let received_actions = extract_actions_sent_to_server(events_sent_to_server); diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 690304bb5c..e9848e702a 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -11,11 +11,13 @@ mod ui; mod wasm_vm; use log::info; +use std::{ + path::PathBuf, + sync::{Arc, Mutex, RwLock}, + thread, +}; use zellij_utils::zellij_tile; -use std::path::PathBuf; -use std::sync::{Arc, Mutex, RwLock}; -use std::thread; use wasmer::Store; use zellij_tile::data::{Event, Palette, PluginCapabilities}; @@ -34,7 +36,7 @@ use zellij_utils::{ input::{ command::{RunCommand, TerminalAction}, get_mode_info, - layout::Layout, + layout::LayoutFromYaml, options::Options, }, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, @@ -44,7 +46,12 @@ use zellij_utils::{ /// Instructions related to server-side application #[derive(Debug, Clone)] pub(crate) enum ServerInstruction { - NewClient(ClientAttributes, Box, Box, Option), + NewClient( + ClientAttributes, + Box, + Box, + Option, + ), Render(Option), UnblockInputThread, ClientExit, @@ -207,7 +214,7 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { to_server.clone(), client_attributes, session_state.clone(), - layout, + layout.clone(), ); *session_data.write().unwrap() = Some(session); *session_state.write().unwrap() = SessionState::Attached; @@ -219,14 +226,31 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { }) }); - session_data - .read() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_pty(PtyInstruction::NewTab(default_shell.clone())) - .unwrap(); + let spawn_tabs = |tab_layout| { + session_data + .read() + .unwrap() + .as_ref() + .unwrap() + .senders + .send_to_pty(PtyInstruction::NewTab(default_shell.clone(), tab_layout)) + .unwrap() + }; + + match layout { + None => { + spawn_tabs(None); + } + Some(layout) => { + if !&layout.tabs.is_empty() { + for tab_layout in layout.tabs { + spawn_tabs(Some(tab_layout.clone())); + } + } else { + spawn_tabs(None); + } + } + } } ServerInstruction::AttachClient(attrs, _, options) => { *session_state.write().unwrap() = SessionState::Attached; @@ -302,7 +326,7 @@ fn init_session( to_server: SenderWithContext, client_attributes: ClientAttributes, session_state: Arc>, - layout: Option, + layout: Option, ) -> SessionMetaData { let (to_screen, screen_receiver): ChannelWithContext = channels::unbounded(); let to_screen = SenderWithContext::new(to_screen); diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 24d405835c..198a148703 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -1,5 +1,6 @@ use std::env; use std::os::unix::io::RawFd; +use std::os::unix::process::CommandExt; use std::path::PathBuf; use std::process::{Child, Command}; use std::sync::{Arc, Mutex}; @@ -102,10 +103,21 @@ fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, P let pid_secondary = match fork_pty_res.fork_result { ForkResult::Parent { child } => child, ForkResult::Child => { - let child = Command::new(cmd.command) - .args(&cmd.args) - .spawn() - .expect("failed to spawn"); + let child = unsafe { + Command::new(cmd.command) + .args(&cmd.args) + .pre_exec(|| -> std::io::Result<()> { + // this is the "unsafe" part, for more details please see: + // https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#notes-and-safety + unistd::setpgid(Pid::from_raw(0), Pid::from_raw(0)) + .expect("failed to create a new process group"); + Ok(()) + }) + .spawn() + .expect("failed to spawn") + }; + unistd::tcsetpgrp(0, Pid::from_raw(child.id() as i32)) + .expect("faled to set child's forceground process group"); handle_command_exit(child); ::std::process::exit(0); } diff --git a/zellij-server/src/panes/alacritty_functions.rs b/zellij-server/src/panes/alacritty_functions.rs new file mode 100644 index 0000000000..948e1f7667 --- /dev/null +++ b/zellij-server/src/panes/alacritty_functions.rs @@ -0,0 +1,298 @@ +use crate::panes::AnsiCode; +use std::convert::TryFrom; + +pub fn parse_sgr_color(params: &mut dyn Iterator) -> Option { + match params.next() { + Some(2) => Some(AnsiCode::RgbCode(( + u8::try_from(params.next()?).ok()?, + u8::try_from(params.next()?).ok()?, + u8::try_from(params.next()?).ok()?, + ))), + Some(5) => Some(AnsiCode::ColorIndex(u8::try_from(params.next()?).ok()?)), + _ => None, + } +} + +/// Parse colors in XParseColor format. +pub fn xparse_color(color: &[u8]) -> Option { + if !color.is_empty() && color[0] == b'#' { + parse_legacy_color(&color[1..]) + } else if color.len() >= 4 && &color[..4] == b"rgb:" { + parse_rgb_color(&color[4..]) + } else { + None + } +} + +/// Parse colors in `rgb:r(rrr)/g(ggg)/b(bbb)` format. +pub fn parse_rgb_color(color: &[u8]) -> Option { + let colors = std::str::from_utf8(color) + .ok()? + .split('/') + .collect::>(); + + if colors.len() != 3 { + return None; + } + + // Scale values instead of filling with `0`s. + let scale = |input: &str| { + if input.len() > 4 { + None + } else { + let max = u32::pow(16, input.len() as u32) - 1; + let value = u32::from_str_radix(input, 16).ok()?; + Some((255 * value / max) as u8) + } + }; + + Some(AnsiCode::RgbCode(( + scale(colors[0])?, + scale(colors[1])?, + scale(colors[2])?, + ))) +} + +/// Parse colors in `#r(rrr)g(ggg)b(bbb)` format. +pub fn parse_legacy_color(color: &[u8]) -> Option { + let item_len = color.len() / 3; + + // Truncate/Fill to two byte precision. + let color_from_slice = |slice: &[u8]| { + let col = usize::from_str_radix(std::str::from_utf8(slice).ok()?, 16).ok()? << 4; + Some((col >> (4 * slice.len().saturating_sub(1))) as u8) + }; + + Some(AnsiCode::RgbCode(( + color_from_slice(&color[0..item_len])?, + color_from_slice(&color[item_len..item_len * 2])?, + color_from_slice(&color[item_len * 2..])?, + ))) +} + +pub fn parse_number(input: &[u8]) -> Option { + if input.is_empty() { + return None; + } + let mut num: u8 = 0; + for c in input { + let c = *c as char; + if let Some(digit) = c.to_digit(10) { + num = match num.checked_mul(10).and_then(|v| v.checked_add(digit as u8)) { + Some(v) => v, + None => return None, + } + } else { + return None; + } + } + Some(num) +} + +// these functions are copied verbatim (with slight modifications) from alacritty, mainly in order +// to be able to use the VTE API provided by their great package of the same name more easily +// The following license refers to this file and the functions +// within it only +// +// Apache License +// Version 2.0, January 2004 +// http://www.apache.org/licenses/ +// +// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +// +// 1. Definitions. +// +// "License" shall mean the terms and conditions for use, reproduction, +// and distribution as defined by Sections 1 through 9 of this document. +// +// "Licensor" shall mean the copyright owner or entity authorized by +// the copyright owner that is granting the License. +// +// "Legal Entity" shall mean the union of the acting entity and all +// other entities that control, are controlled by, or are under common +// control with that entity. For the purposes of this definition, +// "control" means (i) the power, direct or indirect, to cause the +// direction or management of such entity, whether by contract or +// otherwise, or (ii) ownership of fifty percent (50%) or more of the +// outstanding shares, or (iii) beneficial ownership of such entity. +// +// "You" (or "Your") shall mean an individual or Legal Entity +// exercising permissions granted by this License. +// +// "Source" form shall mean the preferred form for making modifications, +// including but not limited to software source code, documentation +// source, and configuration files. +// +// "Object" form shall mean any form resulting from mechanical +// transformation or translation of a Source form, including but +// not limited to compiled object code, generated documentation, +// and conversions to other media types. +// +// "Work" shall mean the work of authorship, whether in Source or +// Object form, made available under the License, as indicated by a +// copyright notice that is included in or attached to the work +// (an example is provided in the Appendix below). +// +// "Derivative Works" shall mean any work, whether in Source or Object +// form, that is based on (or derived from) the Work and for which the +// editorial revisions, annotations, elaborations, or other modifications +// represent, as a whole, an original work of authorship. For the purposes +// of this License, Derivative Works shall not include works that remain +// separable from, or merely link (or bind by name) to the interfaces of, +// the Work and Derivative Works thereof. +// +// "Contribution" shall mean any work of authorship, including +// the original version of the Work and any modifications or additions +// to that Work or Derivative Works thereof, that is intentionally +// submitted to Licensor for inclusion in the Work by the copyright owner +// or by an individual or Legal Entity authorized to submit on behalf of +// the copyright owner. For the purposes of this definition, "submitted" +// means any form of electronic, verbal, or written communication sent +// to the Licensor or its representatives, including but not limited to +// communication on electronic mailing lists, source code control systems, +// and issue tracking systems that are managed by, or on behalf of, the +// Licensor for the purpose of discussing and improving the Work, but +// excluding communication that is conspicuously marked or otherwise +// designated in writing by the copyright owner as "Not a Contribution." +// +// "Contributor" shall mean Licensor and any individual or Legal Entity +// on behalf of whom a Contribution has been received by Licensor and +// subsequently incorporated within the Work. +// +// 2. Grant of Copyright License. Subject to the terms and conditions of +// this License, each Contributor hereby grants to You a perpetual, +// worldwide, non-exclusive, no-charge, royalty-free, irrevocable +// copyright license to reproduce, prepare Derivative Works of, +// publicly display, publicly perform, sublicense, and distribute the +// Work and such Derivative Works in Source or Object form. +// +// 3. Grant of Patent License. Subject to the terms and conditions of +// this License, each Contributor hereby grants to You a perpetual, +// worldwide, non-exclusive, no-charge, royalty-free, irrevocable +// (except as stated in this section) patent license to make, have made, +// use, offer to sell, sell, import, and otherwise transfer the Work, +// where such license applies only to those patent claims licensable +// by such Contributor that are necessarily infringed by their +// Contribution(s) alone or by combination of their Contribution(s) +// with the Work to which such Contribution(s) was submitted. If You +// institute patent litigation against any entity (including a +// cross-claim or counterclaim in a lawsuit) alleging that the Work +// or a Contribution incorporated within the Work constitutes direct +// or contributory patent infringement, then any patent licenses +// granted to You under this License for that Work shall terminate +// as of the date such litigation is filed. +// +// 4. Redistribution. You may reproduce and distribute copies of the +// Work or Derivative Works thereof in any medium, with or without +// modifications, and in Source or Object form, provided that You +// meet the following conditions: +// +// (a) You must give any other recipients of the Work or +// Derivative Works a copy of this License; and +// +// (b) You must cause any modified files to carry prominent notices +// stating that You changed the files; and +// +// (c) You must retain, in the Source form of any Derivative Works +// that You distribute, all copyright, patent, trademark, and +// attribution notices from the Source form of the Work, +// excluding those notices that do not pertain to any part of +// the Derivative Works; and +// +// (d) If the Work includes a "NOTICE" text file as part of its +// distribution, then any Derivative Works that You distribute must +// include a readable copy of the attribution notices contained +// within such NOTICE file, excluding those notices that do not +// pertain to any part of the Derivative Works, in at least one +// of the following places: within a NOTICE text file distributed +// as part of the Derivative Works; within the Source form or +// documentation, if provided along with the Derivative Works; or, +// within a display generated by the Derivative Works, if and +// wherever such third-party notices normally appear. The contents +// of the NOTICE file are for informational purposes only and +// do not modify the License. You may add Your own attribution +// notices within Derivative Works that You distribute, alongside +// or as an addendum to the NOTICE text from the Work, provided +// that such additional attribution notices cannot be construed +// as modifying the License. +// +// You may add Your own copyright statement to Your modifications and +// may provide additional or different license terms and conditions +// for use, reproduction, or distribution of Your modifications, or +// for any such Derivative Works as a whole, provided Your use, +// reproduction, and distribution of the Work otherwise complies with +// the conditions stated in this License. +// +// 5. Submission of Contributions. Unless You explicitly state otherwise, +// any Contribution intentionally submitted for inclusion in the Work +// by You to the Licensor shall be under the terms and conditions of +// this License, without any additional terms or conditions. +// Notwithstanding the above, nothing herein shall supersede or modify +// the terms of any separate license agreement you may have executed +// with Licensor regarding such Contributions. +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor, +// except as required for reasonable and customary use in describing the +// origin of the Work and reproducing the content of the NOTICE file. +// +// 7. Disclaimer of Warranty. Unless required by applicable law or +// agreed to in writing, Licensor provides the Work (and each +// Contributor provides its Contributions) on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied, including, without limitation, any warranties or conditions +// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +// PARTICULAR PURPOSE. You are solely responsible for determining the +// appropriateness of using or redistributing the Work and assume any +// risks associated with Your exercise of permissions under this License. +// +// 8. Limitation of Liability. In no event and under no legal theory, +// whether in tort (including negligence), contract, or otherwise, +// unless required by applicable law (such as deliberate and grossly +// negligent acts) or agreed to in writing, shall any Contributor be +// liable to You for damages, including any direct, indirect, special, +// incidental, or consequential damages of any character arising as a +// result of this License or out of the use or inability to use the +// Work (including but not limited to damages for loss of goodwill, +// work stoppage, computer failure or malfunction, or any and all +// other commercial damages or losses), even if such Contributor +// has been advised of the possibility of such damages. +// +// 9. Accepting Warranty or Additional Liability. While redistributing +// the Work or Derivative Works thereof, You may choose to offer, +// and charge a fee for, acceptance of support, warranty, indemnity, +// or other liability obligations and/or rights consistent with this +// License. However, in accepting such obligations, You may act only +// on Your own behalf and on Your sole responsibility, not on behalf +// of any other Contributor, and only if You agree to indemnify, +// defend, and hold each Contributor harmless for any liability +// incurred by, or claims asserted against, such Contributor by reason +// of your accepting any such warranty or additional liability. +// +// END OF TERMS AND CONDITIONS +// +// APPENDIX: How to apply the Apache License to your work. +// +// To apply the Apache License to your work, attach the following +// boilerplate notice, with the fields enclosed by brackets "[]" +// replaced with your own identifying information. (Don't include +// the brackets!) The text should be enclosed in the appropriate +// comment syntax for the file format. We also recommend that a +// file or class name and description of purpose be included on the +// same "printed page" as the copyright notice for easier +// identification within third-party archives. +// +// Copyright 2020 The Alacritty Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index df9b7a9cec..07808b03b3 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -10,39 +10,21 @@ 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}; use zellij_utils::{consts::VERSION, logging::debug_log_to_file, shared::version_number}; +use crate::panes::alacritty_functions::{parse_number, xparse_color}; use crate::panes::terminal_character::{ - CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, TerminalCharacter, - EMPTY_TERMINAL_CHARACTER, + AnsiCode, CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, + TerminalCharacter, EMPTY_TERMINAL_CHARACTER, }; use super::selection::Selection; -// this was copied verbatim from alacritty -fn parse_number(input: &[u8]) -> Option { - if input.is_empty() { - return None; - } - let mut num: u8 = 0; - for c in input { - let c = *c as char; - if let Some(digit) = c.to_digit(10) { - num = match num.checked_mul(10).and_then(|v| v.checked_add(digit as u8)) { - Some(v) => v, - None => return None, - } - } else { - return None; - } - } - Some(num) -} - fn get_top_non_canonical_rows(rows: &mut Vec) -> Vec { let mut index_of_last_non_canonical_row = None; for (i, row) in rows.iter().enumerate() { @@ -60,7 +42,7 @@ fn get_top_non_canonical_rows(rows: &mut Vec) -> Vec { } } -fn get_bottom_canonical_row_and_wraps(rows: &mut VecDeque) -> Vec { +fn get_lines_above_bottom_canonical_row_and_wraps(rows: &mut VecDeque) -> Vec { let mut index_of_last_non_canonical_row = None; for (i, row) in rows.iter().enumerate().rev() { index_of_last_non_canonical_row = Some(i); @@ -76,101 +58,158 @@ fn get_bottom_canonical_row_and_wraps(rows: &mut VecDeque) -> Vec { } } -fn transfer_rows_down( - source: &mut VecDeque, - destination: &mut Vec, +fn get_viewport_bottom_canonical_row_and_wraps(viewport: &mut Vec) -> Vec { + let mut index_of_last_non_canonical_row = None; + for (i, row) in viewport.iter().enumerate().rev() { + index_of_last_non_canonical_row = Some(i); + if row.is_canonical { + break; + } + } + match index_of_last_non_canonical_row { + Some(index_of_last_non_canonical_row) => { + viewport.drain(index_of_last_non_canonical_row..).collect() + } + None => vec![], + } +} + +fn get_top_canonical_row_and_wraps(rows: &mut Vec) -> Vec { + let mut index_of_first_non_canonical_row = None; + let mut end_index_of_first_canonical_line = None; + for (i, row) in rows.iter().enumerate() { + if row.is_canonical && end_index_of_first_canonical_line.is_none() { + index_of_first_non_canonical_row = Some(i); + end_index_of_first_canonical_line = Some(i); + continue; + } + if row.is_canonical && end_index_of_first_canonical_line.is_some() { + break; + } + if index_of_first_non_canonical_row.is_some() { + end_index_of_first_canonical_line = Some(i); + continue; + } + } + match ( + index_of_first_non_canonical_row, + end_index_of_first_canonical_line, + ) { + (Some(first_index), Some(last_index)) => rows.drain(first_index..=last_index).collect(), + (Some(first_index), None) => rows.drain(first_index..).collect(), + _ => vec![], + } +} + +fn transfer_rows_from_lines_above_to_viewport( + lines_above: &mut VecDeque, + viewport: &mut Vec, count: usize, - max_src_width: Option, - max_dst_width: Option, + max_viewport_width: usize, ) { let mut next_lines: Vec = vec![]; - let mut lines_added_to_destination: isize = 0; + let mut lines_added_to_viewport: isize = 0; loop { - if lines_added_to_destination as usize == count { + if lines_added_to_viewport as usize == count { break; } if next_lines.is_empty() { - match source.pop_back() { + match lines_above.pop_back() { Some(next_line) => { - let mut top_non_canonical_rows_in_dst = get_top_non_canonical_rows(destination); - lines_added_to_destination -= top_non_canonical_rows_in_dst.len() as isize; + let mut top_non_canonical_rows_in_dst = get_top_non_canonical_rows(viewport); + lines_added_to_viewport -= top_non_canonical_rows_in_dst.len() as isize; next_lines.push(next_line); next_lines.append(&mut top_non_canonical_rows_in_dst); - next_lines = match max_dst_width { - Some(max_row_width) => Row::from_rows(next_lines, max_row_width) - .split_to_rows_of_length(max_row_width), - None => vec![Row::from_rows(next_lines, 0)], - }; + next_lines = Row::from_rows(next_lines, max_viewport_width) + .split_to_rows_of_length(max_viewport_width); if next_lines.is_empty() { - // no more lines at source, the line we popped was probably empty + // no more lines at lines_above, the line we popped was probably empty break; } } None => break, // no more rows } } - destination.insert(0, next_lines.pop().unwrap()); - lines_added_to_destination += 1; + viewport.insert(0, next_lines.pop().unwrap()); + lines_added_to_viewport += 1; } if !next_lines.is_empty() { - match max_src_width { - Some(max_row_width) => { - let excess_rows = Row::from_rows(next_lines, max_row_width) - .split_to_rows_of_length(max_row_width); - source.extend(excess_rows); - } - None => { - let excess_row = Row::from_rows(next_lines, 0); - bounded_push(source, excess_row); - } - } + let excess_row = Row::from_rows(next_lines, 0); + bounded_push(lines_above, excess_row); } } -fn transfer_rows_up( - source: &mut Vec, - destination: &mut VecDeque, +fn transfer_rows_from_viewport_to_lines_above( + viewport: &mut Vec, + lines_above: &mut VecDeque, count: usize, - max_src_width: Option, - max_dst_width: Option, + max_viewport_width: usize, ) { let mut next_lines: Vec = vec![]; for _ in 0..count { if next_lines.is_empty() { - if !source.is_empty() { - let next_line = source.remove(0); + if !viewport.is_empty() { + let next_line = viewport.remove(0); if !next_line.is_canonical { let mut bottom_canonical_row_and_wraps_in_dst = - get_bottom_canonical_row_and_wraps(destination); + get_lines_above_bottom_canonical_row_and_wraps(lines_above); next_lines.append(&mut bottom_canonical_row_and_wraps_in_dst); } next_lines.push(next_line); - next_lines = match max_dst_width { - Some(max_row_width) => Row::from_rows(next_lines, max_row_width) - .split_to_rows_of_length(max_row_width), - None => vec![Row::from_rows(next_lines, 0)], - }; + next_lines = vec![Row::from_rows(next_lines, 0)]; } else { break; // no more rows } } - bounded_push(destination, next_lines.remove(0)); + bounded_push(lines_above, next_lines.remove(0)); } if !next_lines.is_empty() { - match max_src_width { - Some(max_row_width) => { - let excess_rows = Row::from_rows(next_lines, max_row_width) - .split_to_rows_of_length(max_row_width); - for row in excess_rows { - source.insert(0, row); + let excess_rows = Row::from_rows(next_lines, max_viewport_width) + .split_to_rows_of_length(max_viewport_width); + for row in excess_rows { + viewport.insert(0, row); + } + } +} + +fn transfer_rows_from_lines_below_to_viewport( + lines_below: &mut Vec, + viewport: &mut Vec, + count: usize, + max_viewport_width: usize, +) { + let mut next_lines: Vec = vec![]; + for _ in 0..count { + let mut lines_pulled_from_viewport = 0; + if next_lines.is_empty() { + if !lines_below.is_empty() { + let mut top_non_canonical_rows_in_lines_below = + get_top_non_canonical_rows(lines_below); + if !top_non_canonical_rows_in_lines_below.is_empty() { + let mut canonical_line = get_viewport_bottom_canonical_row_and_wraps(viewport); + lines_pulled_from_viewport += canonical_line.len(); + canonical_line.append(&mut top_non_canonical_rows_in_lines_below); + next_lines = Row::from_rows(canonical_line, max_viewport_width) + .split_to_rows_of_length(max_viewport_width); + } else { + let canonical_row = get_top_canonical_row_and_wraps(lines_below); + next_lines = Row::from_rows(canonical_row, max_viewport_width) + .split_to_rows_of_length(max_viewport_width); } + } else { + break; // no more rows } - None => { - let excess_row = Row::from_rows(next_lines, 0); - source.insert(0, excess_row); + } + for _ in 0..(lines_pulled_from_viewport + 1) { + if !next_lines.is_empty() { + viewport.push(next_lines.remove(0)); } } } + if !next_lines.is_empty() { + let excess_row = Row::from_rows(next_lines, 0); + lines_below.insert(0, excess_row); + } } fn bounded_push(vec: &mut VecDeque, value: Row) { @@ -308,6 +347,8 @@ pub struct Grid { preceding_char: Option, colors: Palette, output_buffer: OutputBuffer, + title_stack: Vec, + pub changed_colors: Option<[Option; 256]>, pub should_render: bool, pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "") pub erasure_mode: bool, // ERM @@ -318,6 +359,7 @@ pub struct Grid { pub height: usize, pub pending_messages_to_pty: Vec>, pub selection: Selection, + pub title: Option, } impl Debug for Grid { @@ -358,6 +400,9 @@ impl Grid { colors, output_buffer: Default::default(), selection: Default::default(), + title_stack: vec![], + title: None, + changed_colors: None, } } pub fn render_full_viewport(&mut self) { @@ -404,6 +449,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 +537,14 @@ 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_from_lines_above_to_viewport( + &mut self.lines_above, + &mut self.viewport, + 1, + self.width, + ); + self.selection.move_down(1); } self.output_buffer.update_all_lines(); @@ -491,12 +559,36 @@ impl Grid { last_line_above.append(&mut line_to_push_up.columns); bounded_push(&mut self.lines_above, last_line_above); } - let line_to_insert_at_viewport_bottom = self.lines_below.remove(0); - self.viewport.push(line_to_insert_at_viewport_bottom); + + transfer_rows_from_lines_below_to_viewport( + &mut self.lines_below, + &mut self.viewport, + 1, + self.width, + ); + self.selection.move_up(1); 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 { @@ -563,12 +655,12 @@ impl Grid { match current_viewport_row_count.cmp(&self.height) { Ordering::Less => { let row_count_to_transfer = self.height - current_viewport_row_count; - transfer_rows_down( + + transfer_rows_from_lines_above_to_viewport( &mut self.lines_above, &mut self.viewport, row_count_to_transfer, - None, - Some(new_columns), + new_columns, ); let rows_pulled = self.viewport.len() - current_viewport_row_count; new_cursor_y += rows_pulled; @@ -580,12 +672,11 @@ impl Grid { } else { new_cursor_y -= row_count_to_transfer; } - transfer_rows_up( + transfer_rows_from_viewport_to_lines_above( &mut self.viewport, &mut self.lines_above, row_count_to_transfer, - Some(new_columns), - None, + new_columns, ); } Ordering::Equal => {} @@ -598,12 +689,11 @@ impl Grid { match current_viewport_row_count.cmp(&new_rows) { Ordering::Less => { let row_count_to_transfer = new_rows - current_viewport_row_count; - transfer_rows_down( + transfer_rows_from_lines_above_to_viewport( &mut self.lines_above, &mut self.viewport, row_count_to_transfer, - None, - Some(new_columns), + new_columns, ); let rows_pulled = self.viewport.len() - current_viewport_row_count; self.cursor.y += rows_pulled; @@ -615,12 +705,11 @@ impl Grid { } else { self.cursor.y -= row_count_to_transfer; } - transfer_rows_up( + transfer_rows_from_viewport_to_lines_above( &mut self.viewport, &mut self.lines_above, row_count_to_transfer, - Some(new_columns), - None, + new_columns, ); } Ordering::Equal => {} @@ -764,12 +853,11 @@ impl Grid { } if self.cursor.y == self.height - 1 { let row_count_to_transfer = 1; - transfer_rows_up( + transfer_rows_from_viewport_to_lines_above( &mut self.viewport, &mut self.lines_above, row_count_to_transfer, - Some(self.width), - None, + self.width, ); self.selection.move_up(1); self.output_buffer.update_all_lines(); @@ -839,12 +927,11 @@ impl Grid { self.cursor.x = 0; if self.cursor.y == self.height - 1 { let row_count_to_transfer = 1; - transfer_rows_up( + transfer_rows_from_viewport_to_lines_above( &mut self.viewport, &mut self.lines_above, row_count_to_transfer, - Some(self.width), - None, + self.width, ); let wrapped_row = Row::new(self.width); self.viewport.push(wrapped_row); @@ -981,26 +1068,18 @@ impl Grid { } self.output_buffer.update_all_lines(); } - pub fn move_cursor_down(&mut self, count: usize, pad_character: TerminalCharacter) { + pub fn move_cursor_down_until_edge_of_screen( + &mut self, + count: usize, + pad_character: TerminalCharacter, + ) { if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { if self.cursor.y >= scroll_region_top && self.cursor.y <= scroll_region_bottom { self.cursor.y = std::cmp::min(self.cursor.y + count, scroll_region_bottom); return; } } - let lines_to_add = if self.cursor.y + count > self.height - 1 { - (self.cursor.y + count) - (self.height - 1) - } else { - 0 - }; - self.cursor.y = if self.cursor.y + count > self.height - 1 { - self.height - 1 - } else { - self.cursor.y + count - }; - for _ in 0..lines_to_add { - self.add_canonical_line(); - } + self.cursor.y = std::cmp::min(self.cursor.y + count, self.height - 1); self.pad_lines_until(self.cursor.y, pad_character); } pub fn move_cursor_back(&mut self, count: usize) { @@ -1140,6 +1219,7 @@ impl Grid { self.disable_linewrap = false; self.cursor.change_shape(CursorShape::Block); self.output_buffer.update_all_lines(); + self.changed_colors = None; } fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) { self.preceding_char = Some(terminal_character); @@ -1200,7 +1280,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 +1291,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,11 +1326,28 @@ 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 { fn print(&mut self, c: char) { let c = self.cursor.charsets[self.active_charset].map(c); + // apparently, building TerminalCharacter like this without a "new" method // is a little faster let terminal_character = TerminalCharacter { @@ -1311,24 +1412,30 @@ 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::>() .join(";") .trim() .to_owned(); - // TBD: do something with title? + self.set_title(title); } } // Set color index. b"4" => { - // TBD: set color index - currently unsupported - // - // this changes a terminal color index to something else - // meaning anything set to that index will be changed - // during rendering + for chunk in params[1..].chunks(2) { + let index = parse_number(chunk[0]); + let color = xparse_color(chunk[1]); + if let (Some(i), Some(c)) = (index, color) { + if self.changed_colors.is_none() { + self.changed_colors = Some([None; 256]); + } + self.changed_colors.as_mut().unwrap()[i as usize] = Some(c); + return; + } + } } // Get/set Foreground, Background, Cursor colors. @@ -1402,6 +1509,21 @@ impl Perform for Grid { // Reset color index. b"104" => { + // Reset all color indexes when no parameters are given. + if params.len() == 1 { + self.changed_colors = None; + return; + } + + // Reset color indexes given as parameters. + for param in ¶ms[1..] { + if let Some(index) = parse_number(param) { + if self.changed_colors.is_some() { + self.changed_colors.as_mut().unwrap()[index as usize] = None + } + } + } + // Reset all color indexes when no parameters are given. if params.len() == 1 { // TBD - reset all color changes - currently unsupported @@ -1496,7 +1618,7 @@ impl Perform for Grid { // move cursor down until edge of screen let move_down_count = next_param_or(1); let pad_character = EMPTY_TERMINAL_CHARACTER; - self.move_cursor_down(move_down_count as usize, pad_character); + self.move_cursor_down_until_edge_of_screen(move_down_count as usize, pad_character); } else if c == 'D' { let move_back_count = next_param_or(1); self.move_cursor_back(move_back_count); @@ -1521,7 +1643,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) => { @@ -1676,7 +1798,7 @@ impl Perform for Grid { } else if c == 'E' { let count = next_param_or(1); let pad_character = EMPTY_TERMINAL_CHARACTER; - self.move_cursor_down(count, pad_character); + self.move_cursor_down_until_edge_of_screen(count, pad_character); } else if c == 'F' { let count = next_param_or(1); self.move_cursor_up(count); @@ -1758,10 +1880,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(); } _ => {} } @@ -2085,6 +2207,9 @@ impl Row { if !parts.is_empty() && self.is_canonical { parts.get_mut(0).unwrap().is_canonical = true; } + if parts.is_empty() { + parts.push(self.clone()); + } parts } } diff --git a/zellij-server/src/panes/mod.rs b/zellij-server/src/panes/mod.rs index 3830b24292..c918f98f88 100644 --- a/zellij-server/src/panes/mod.rs +++ b/zellij-server/src/panes/mod.rs @@ -1,9 +1,11 @@ +mod alacritty_functions; mod grid; mod plugin_pane; mod selection; mod terminal_character; mod terminal_pane; +pub use alacritty_functions::*; pub use grid::*; pub(crate) use plugin_pane::*; pub use terminal_character::*; diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index 256ceaea1e..3bac21ebda 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -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, + pub content_position_and_size: PositionAndSize, pub send_plugin_instructions: SenderWithContext, 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, + 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) { + 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); + } } diff --git a/zellij-server/src/panes/terminal_character.rs b/zellij-server/src/panes/terminal_character.rs index ececb2a73d..2e1a018d3a 100644 --- a/zellij-server/src/panes/terminal_character.rs +++ b/zellij-server/src/panes/terminal_character.rs @@ -1,9 +1,11 @@ -use std::convert::TryFrom; use std::fmt::{self, Debug, Display, Formatter}; use std::ops::{Index, IndexMut}; + use zellij_utils::logging::debug_log_to_file; use zellij_utils::vte::ParamsIter; +use crate::panes::alacritty_functions::parse_sgr_color; + pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter { character: ' ', width: 1, @@ -191,6 +193,7 @@ impl CharacterStyles { pub fn update_and_return_diff( &mut self, new_styles: &CharacterStyles, + changed_colors: Option<[Option; 256]>, ) -> Option { let mut diff: Option = None; @@ -319,6 +322,19 @@ impl CharacterStyles { self.italic = new_styles.italic; } } + + if let Some(changed_colors) = changed_colors { + if let Some(AnsiCode::ColorIndex(color_index)) = diff.and_then(|diff| diff.foreground) { + if let Some(changed_color) = changed_colors[color_index as usize] { + diff.as_mut().unwrap().foreground = Some(changed_color); + } + } + if let Some(AnsiCode::ColorIndex(color_index)) = diff.and_then(|diff| diff.background) { + if let Some(changed_color) = changed_colors[color_index as usize] { + diff.as_mut().unwrap().background = Some(changed_color); + } + } + } diff } pub fn reset_all(&mut self) { @@ -757,15 +773,3 @@ impl ::std::fmt::Debug for TerminalCharacter { write!(f, "{}", self.character) } } - -fn parse_sgr_color(params: &mut dyn Iterator) -> Option { - match params.next() { - Some(2) => Some(AnsiCode::RgbCode(( - u8::try_from(params.next()?).ok()?, - u8::try_from(params.next()?).ok()?, - u8::try_from(params.next()?).ok()?, - ))), - Some(5) => Some(AnsiCode::ColorIndex(u8::try_from(params.next()?).ok()?)), - _ => None, - } -} diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 0dc28aa742..7adb9c78f2 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -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, + position_and_size: PositionAndSize, + position_and_size_override: Option, 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) -> Vec { // 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 { @@ -160,8 +193,8 @@ impl Pane for TerminalPane { let mut character_styles = CharacterStyles::new(); if self.grid.clear_viewport_before_rendering { for line_index in 0..self.grid.height { - let x = self.get_x(); - let y = self.get_y(); + 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, @@ -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; @@ -203,8 +236,8 @@ impl Pane for TerminalPane { break; } - if let Some(new_styles) = - character_styles.update_and_return_diff(&t_character.styles) + if let Some(new_styles) = character_styles + .update_and_return_diff(&t_character.styles, self.grid.changed_colors) { vte_output.push_str(&new_styles.to_string()); } @@ -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 { self.grid.get_selected_text() } + + fn set_boundary_color(&mut self, color: Option) { + 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)] diff --git a/zellij-server/src/panes/unit/grid_tests.rs b/zellij-server/src/panes/unit/grid_tests.rs index 0facc06630..b10565fed4 100644 --- a/zellij-server/src/panes/unit/grid_tests.rs +++ b/zellij-server/src/panes/unit/grid_tests.rs @@ -907,3 +907,95 @@ pub fn exa_plus_omf_theme() { } assert_snapshot!(format!("{:?}", grid)); } + +#[test] +pub fn scroll_up() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(10, 50, Palette::default()); + let fixture_name = "scrolling"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + grid.scroll_up_one_line(); + assert_snapshot!(format!("{:?}", grid)); +} + +#[test] +pub fn scroll_down() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(10, 50, Palette::default()); + let fixture_name = "scrolling"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + grid.scroll_up_one_line(); + grid.scroll_down_one_line(); + assert_snapshot!(format!("{:?}", grid)); +} + +#[test] +pub fn scroll_up_with_line_wraps() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(10, 25, Palette::default()); + let fixture_name = "scrolling"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + grid.scroll_up_one_line(); + assert_snapshot!(format!("{:?}", grid)); +} + +#[test] +pub fn scroll_down_with_line_wraps() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(10, 25, Palette::default()); + let fixture_name = "scrolling"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + grid.scroll_up_one_line(); + grid.scroll_down_one_line(); + assert_snapshot!(format!("{:?}", grid)); +} + +#[test] +pub fn scroll_up_decrease_width_and_scroll_down() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(10, 50, Palette::default()); + let fixture_name = "scrolling"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + for _ in 0..10 { + grid.scroll_up_one_line(); + } + grid.change_size(10, 25); + for _ in 0..10 { + grid.scroll_down_one_line(); + } + assert_snapshot!(format!("{:?}", grid)); +} + +#[test] +pub fn scroll_up_increase_width_and_scroll_down() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(10, 25, Palette::default()); + let fixture_name = "scrolling"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + for _ in 0..10 { + grid.scroll_up_one_line(); + } + grid.change_size(10, 50); + for _ in 0..10 { + grid.scroll_down_one_line(); + } + assert_snapshot!(format!("{:?}", grid)); +} diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_down.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_down.snap new file mode 100644 index 0000000000..f9292d2af0 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_down.snap @@ -0,0 +1,16 @@ +--- +source: zellij-server/src/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): line 11aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +01 (C): line 12aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +02 (C): line 13aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +03 (C): line 14aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +04 (C): line 15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +05 (C): line 16aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +06 (C): line 17aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +07 (C): line 18aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +08 (C): line 19aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +09 (C): line 20aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_down_with_line_wraps.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_down_with_line_wraps.snap new file mode 100644 index 0000000000..989b15312c --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_down_with_line_wraps.snap @@ -0,0 +1,16 @@ +--- +source: zellij-server/src/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): line 16aaaaaaaaaaaaaaaaaa +01 (W): aaaaaaaaaaaaaaaaaaaaaaaa +02 (C): line 17aaaaaaaaaaaaaaaaaa +03 (W): aaaaaaaaaaaaaaaaaaaaaaaa +04 (C): line 18aaaaaaaaaaaaaaaaaa +05 (W): aaaaaaaaaaaaaaaaaaaaaaaa +06 (C): line 19aaaaaaaaaaaaaaaaaa +07 (W): aaaaaaaaaaaaaaaaaaaaaaaa +08 (C): line 20aaaaaaaaaaaaaaaaaa +09 (W): aaaaaaaaaaaaaaaaaaaaaaaa + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up.snap new file mode 100644 index 0000000000..612b61c755 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up.snap @@ -0,0 +1,16 @@ +--- +source: zellij-server/src/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): line 10aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +01 (C): line 11aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +02 (C): line 12aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +03 (C): line 13aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +04 (C): line 14aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +05 (C): line 15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +06 (C): line 16aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +07 (C): line 17aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +08 (C): line 18aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +09 (C): line 19aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up_decrease_width_and_scroll_down.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up_decrease_width_and_scroll_down.snap new file mode 100644 index 0000000000..fb52f4ede3 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up_decrease_width_and_scroll_down.snap @@ -0,0 +1,16 @@ +--- +source: zellij-server/src/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): line 11aaaaaaaaaaaaaaaaaa +01 (W): aaaaaaaaaaaaaaaaaaaaaaaa +02 (C): line 12aaaaaaaaaaaaaaaaaa +03 (W): aaaaaaaaaaaaaaaaaaaaaaaa +04 (C): line 13aaaaaaaaaaaaaaaaaa +05 (W): aaaaaaaaaaaaaaaaaaaaaaaa +06 (C): line 14aaaaaaaaaaaaaaaaaa +07 (W): aaaaaaaaaaaaaaaaaaaaaaaa +08 (C): line 15aaaaaaaaaaaaaaaaaa +09 (W): aaaaaaaaaaaaaaaaaaaaaaaa + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up_increase_width_and_scroll_down.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up_increase_width_and_scroll_down.snap new file mode 100644 index 0000000000..f9292d2af0 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up_increase_width_and_scroll_down.snap @@ -0,0 +1,16 @@ +--- +source: zellij-server/src/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): line 11aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +01 (C): line 12aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +02 (C): line 13aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +03 (C): line 14aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +04 (C): line 15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +05 (C): line 16aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +06 (C): line 17aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +07 (C): line 18aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +08 (C): line 19aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +09 (C): line 20aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up_with_line_wraps.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up_with_line_wraps.snap new file mode 100644 index 0000000000..0a5acf87a3 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__scroll_up_with_line_wraps.snap @@ -0,0 +1,16 @@ +--- +source: zellij-server/src/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (W): aaaaaaaaaaaaaaaaaaaaaaaa +01 (C): line 16aaaaaaaaaaaaaaaaaa +02 (W): aaaaaaaaaaaaaaaaaaaaaaaa +03 (C): line 17aaaaaaaaaaaaaaaaaa +04 (W): aaaaaaaaaaaaaaaaaaaaaaaa +05 (C): line 18aaaaaaaaaaaaaaaaaa +06 (W): aaaaaaaaaaaaaaaaaaaaaaaa +07 (C): line 19aaaaaaaaaaaaaaaaaa +08 (W): aaaaaaaaaaaaaaaaaaaaaaaa +09 (C): line 20aaaaaaaaaaaaaaaaaa + diff --git a/zellij-server/src/panes/unit/terminal_pane_tests.rs b/zellij-server/src/panes/unit/terminal_pane_tests.rs index f3abf91a2c..70e4e17248 100644 --- a/zellij-server/src/panes/unit/terminal_pane_tests.rs +++ b/zellij-server/src/panes/unit/terminal_pane_tests.rs @@ -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)); diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 5ef3b3217c..011b45cab2 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -20,7 +20,7 @@ use zellij_utils::{ errors::{get_current_ctx, ContextType, PtyContext}, input::{ command::TerminalAction, - layout::{Layout, Run}, + layout::{Layout, LayoutFromYaml, Run, TabLayout}, }, logging::debug_to_file, }; @@ -33,7 +33,7 @@ pub(crate) enum PtyInstruction { SpawnTerminal(Option), SpawnTerminalVertically(Option), SpawnTerminalHorizontally(Option), - NewTab(Option), + NewTab(Option, Option), ClosePane(PaneId), CloseTab(Vec), Exit, @@ -47,7 +47,7 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally, PtyInstruction::ClosePane(_) => PtyContext::ClosePane, PtyInstruction::CloseTab(_) => PtyContext::CloseTab, - PtyInstruction::NewTab(_) => PtyContext::NewTab, + PtyInstruction::NewTab(..) => PtyContext::NewTab, PtyInstruction::Exit => PtyContext::Exit, } } @@ -60,7 +60,7 @@ pub(crate) struct Pty { task_handles: HashMap>, } -pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option) { +pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option) { loop { let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel"); err_ctx.add_call(ContextType::Pty((&event).into())); @@ -86,11 +86,12 @@ pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option) { .send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) .unwrap(); } - PtyInstruction::NewTab(terminal_action) => { + PtyInstruction::NewTab(terminal_action, tab_layout) => { if let Some(layout) = maybe_layout.clone() { - pty.spawn_terminals_for_layout(layout, terminal_action); + let merged_layout = layout.template.insert_tab_layout(tab_layout); + pty.spawn_terminals_for_layout(merged_layout.into(), terminal_action.clone()); } else { - let pid = pty.spawn_terminal(terminal_action); + let pid = pty.spawn_terminal(terminal_action.clone()); pty.bus .senders .send_to_screen(ScreenInstruction::NewTab(pid)) diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 29a864aa1f..a88d04b09d 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -23,7 +23,17 @@ fn route_action( to_server: &SenderWithContext, ) -> bool { let mut should_break = false; + session + .senders + .send_to_plugin(PluginInstruction::Update(None, Event::InputReceived)) + .unwrap(); match action { + Action::ToggleTab => { + session + .senders + .send_to_screen(ScreenInstruction::ToggleTab) + .unwrap(); + } Action::Write(val) => { session .senders @@ -151,6 +161,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 { @@ -181,11 +197,11 @@ fn route_action( .send_to_screen(ScreenInstruction::CloseFocusedPane) .unwrap(); } - Action::NewTab => { + Action::NewTab(tab_layout) => { let shell = session.default_shell.clone(); session .senders - .send_to_pty(PtyInstruction::NewTab(shell)) + .send_to_pty(PtyInstruction::NewTab(shell, tab_layout)) .unwrap(); } Action::GoToNextTab => { diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index b348540fb6..d0e02e80e5 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -56,6 +56,7 @@ pub(crate) enum ScreenInstruction { ClearScroll, CloseFocusedPane, ToggleActiveTerminalFullscreen, + TogglePaneFrames, SetSelectable(PaneId, bool, usize), SetFixedHeight(PaneId, usize, usize), SetFixedWidth(PaneId, usize, usize), @@ -68,6 +69,7 @@ pub(crate) enum ScreenInstruction { ToggleActiveSyncTab, CloseTab, GoToTab(u32), + ToggleTab, UpdateTabName(Vec), TerminalResize(PositionAndSize), ChangeMode(ModeInfo), @@ -112,6 +114,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, @@ -133,6 +136,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::MouseRelease(_) => ScreenContext::MouseRelease, ScreenInstruction::MouseHold(_) => ScreenContext::MouseHold, ScreenInstruction::Copy => ScreenContext::Copy, + ScreenInstruction::ToggleTab => ScreenContext::ToggleTab, } } } @@ -150,9 +154,11 @@ pub(crate) struct Screen { position_and_size: PositionAndSize, /// The index of this [`Screen`]'s active [`Tab`]. active_tab_index: Option, + tab_history: Vec>, mode_info: ModeInfo, colors: Palette, session_state: Arc>, + draw_pane_frames: bool, } impl Screen { @@ -163,6 +169,7 @@ impl Screen { max_panes: Option, mode_info: ModeInfo, session_state: Arc>, + draw_pane_frames: bool, ) -> Self { Screen { bus, @@ -171,8 +178,10 @@ impl Screen { colors: client_attributes.palette, active_tab_index: None, tabs: BTreeMap::new(), + tab_history: Vec::with_capacity(32), mode_info, session_state, + draw_pane_frames, } } @@ -193,7 +202,9 @@ impl Screen { self.mode_info.clone(), self.colors, self.session_state.clone(), + self.draw_pane_frames, ); + self.tab_history.push(self.active_tab_index); self.active_tab_index = Some(tab_index); self.tabs.insert(tab_index, tab); self.update_tabs(); @@ -219,6 +230,8 @@ impl Screen { for tab in self.tabs.values_mut() { if tab.position == new_tab_pos { tab.set_force_render(); + self.tab_history.retain(|&e| e != Some(tab.index)); + self.tab_history.push(self.active_tab_index); self.active_tab_index = Some(tab.index); break; } @@ -238,6 +251,8 @@ impl Screen { for tab in self.tabs.values_mut() { if tab.position == new_tab_pos { tab.set_force_render(); + self.tab_history.retain(|&e| e != Some(tab.index)); + self.tab_history.push(self.active_tab_index); self.active_tab_index = Some(tab.index); break; } @@ -252,6 +267,8 @@ impl Screen { if let Some(t) = self.tabs.values_mut().find(|t| t.position == tab_index) { if t.index != active_tab_index { t.set_force_render(); + self.tab_history.retain(|&e| e != Some(t.index)); + self.tab_history.push(self.active_tab_index); self.active_tab_index = Some(t.index); self.update_tabs(); self.render(); @@ -263,9 +280,6 @@ impl Screen { /// to be the last tab. pub fn close_tab(&mut self) { let active_tab_index = self.active_tab_index.unwrap(); - if self.tabs.len() > 1 { - self.switch_tab_prev(); - } let active_tab = self.tabs.remove(&active_tab_index).unwrap(); let pane_ids = active_tab.get_pane_ids(); // below we don't check the result of sending the CloseTab instruction to the pty thread @@ -284,6 +298,7 @@ impl Screen { .unwrap(); } } else { + self.active_tab_index = self.tab_history.pop().unwrap(); for t in self.tabs.values_mut() { if t.position > active_tab.position { t.position -= 1; @@ -329,6 +344,17 @@ impl Screen { } } + /// Returns an immutable reference to this [`Screen`]'s previous active [`Tab`]. + /// Consumes the last entry in tab history. + pub fn get_previous_tab(&mut self) -> Option<&Tab> { + let last = self.tab_history.pop(); + last?; + match last.unwrap() { + Some(tab) => self.tabs.get(&tab), + None => None, + } + } + /// Returns a mutable reference to this [`Screen`]'s active [`Tab`]. pub fn get_active_tab_mut(&mut self) -> Option<&mut Tab> { match self.active_tab_index { @@ -359,8 +385,10 @@ 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.tab_history.push(self.active_tab_index); self.active_tab_index = Some(tab_index); self.tabs.insert(tab_index, tab); self.update_tabs(); @@ -405,6 +433,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) { @@ -417,6 +446,16 @@ impl Screen { self.switch_tab_next(); } } + pub fn toggle_tab(&mut self) { + let tab = self.get_previous_tab(); + if let Some(t) = tab { + let position = t.position; + self.go_to_tab(position + 1); + }; + + self.update_tabs(); + self.render(); + } } // The box is here in order to make the @@ -430,6 +469,7 @@ pub(crate) fn screen_thread_main( session_state: Arc>, ) { let capabilities = config_options.simplified_ui; + let draw_pane_frames = !config_options.no_pane_frames; let mut screen = Screen::new( bus, @@ -443,6 +483,7 @@ pub(crate) fn screen_thread_main( }, ), session_state, + draw_pane_frames, ); loop { let (event, mut err_ctx) = screen @@ -663,6 +704,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 @@ -751,6 +799,14 @@ pub(crate) fn screen_thread_main( ScreenInstruction::Exit => { break; } + ScreenInstruction::ToggleTab => { + screen.toggle_tab(); + screen + .bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); + } } } } diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index 0662be2256..6ecf33b71c 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -24,53 +24,73 @@ use std::{ cmp::Reverse, collections::{BTreeMap, HashSet}, }; -use zellij_tile::data::{Event, ModeInfo, Palette}; +use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PaletteColor}; use zellij_utils::{ input::{ layout::{Layout, Run}, parse_keys, }, pane_size::PositionAndSize, - shared::adjust_to_size, }; const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this // MIN_TERMINAL_HEIGHT here must be larger than the height of any of the status bars // this is a dirty hack until we implement fixed panes -const MIN_TERMINAL_HEIGHT: usize = 3; -const MIN_TERMINAL_WIDTH: usize = 4; +const MIN_TERMINAL_HEIGHT: usize = 5; +const MIN_TERMINAL_WIDTH: usize = 5; type BorderAndPaneIds = (usize, Vec); -fn split_vertically_with_gap(rect: &PositionAndSize) -> (PositionAndSize, PositionAndSize) { - let width_of_each_half = (rect.cols - 1) / 2; +fn split_vertically(rect: &PositionAndSize) -> (PositionAndSize, PositionAndSize) { + let width_of_each_half = rect.cols / 2; let mut first_rect = *rect; let mut second_rect = *rect; if rect.cols % 2 == 0 { - first_rect.cols = width_of_each_half + 1; - } else { first_rect.cols = width_of_each_half; + } else { + first_rect.cols = width_of_each_half + 1; } - second_rect.x = first_rect.x + first_rect.cols + 1; + second_rect.x = first_rect.x + first_rect.cols; second_rect.cols = width_of_each_half; (first_rect, second_rect) } -fn split_horizontally_with_gap(rect: &PositionAndSize) -> (PositionAndSize, PositionAndSize) { - let height_of_each_half = (rect.rows - 1) / 2; +fn split_horizontally(rect: &PositionAndSize) -> (PositionAndSize, PositionAndSize) { + let height_of_each_half = rect.rows / 2; let mut first_rect = *rect; let mut second_rect = *rect; if rect.rows % 2 == 0 { - first_rect.rows = height_of_each_half + 1; - } else { first_rect.rows = height_of_each_half; + } else { + first_rect.rows = height_of_each_half + 1; } - second_rect.y = first_rect.y + first_rect.rows + 1; + second_rect.y = first_rect.y + first_rect.rows; second_rect.rows = height_of_each_half; (first_rect, second_rect) } +fn pane_content_offset( + position_and_size: &PositionAndSize, + viewport: &PositionAndSize, +) -> (usize, usize) { + // (columns_offset, rows_offset) + // if the pane is not on the bottom or right edge on the screen, we need to reserve one space + // from its content to leave room for the boundary between it and the next pane (if it doesn't + // draw its own frame) + let columns_offset = if position_and_size.x + position_and_size.cols < viewport.cols { + 1 + } else { + 0 + }; + let rows_offset = if position_and_size.y + position_and_size.rows < viewport.rows { + 1 + } else { + 0 + }; + (columns_offset, rows_offset) +} + pub(crate) struct Tab { pub index: usize, pub position: usize, @@ -79,7 +99,8 @@ pub(crate) struct Tab { panes_to_hide: HashSet, active_terminal: Option, max_panes: Option, - full_screen_ws: PositionAndSize, + viewport: PositionAndSize, // includes all selectable panes + display_area: PositionAndSize, // includes all panes (including eg. the status bar and tab bar in the default layout) fullscreen_is_active: bool, os_api: Box, pub senders: ThreadSenders, @@ -88,6 +109,7 @@ pub(crate) struct Tab { session_state: Arc>, pub mode_info: ModeInfo, pub colors: Palette, + draw_pane_frames: bool, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -117,6 +139,7 @@ pub trait Pane { fn position_and_size_override(&self) -> Option; fn should_render(&self) -> bool; fn set_should_render(&mut self, should_render: bool); + fn set_should_render_boundaries(&mut self, _should_render: bool) {} fn selectable(&self) -> bool; fn set_selectable(&mut self, selectable: bool); fn set_invisible_borders(&mut self, invisible_borders: bool); @@ -165,19 +188,19 @@ pub trait Pane { self.y() + self.rows() } fn is_directly_right_of(&self, other: &dyn Pane) -> bool { - self.x() == other.x() + other.columns() + 1 + self.x() == other.x() + other.columns() } fn is_directly_left_of(&self, other: &dyn Pane) -> bool { - self.x() + self.columns() + 1 == other.x() + self.x() + self.columns() == other.x() } fn is_directly_below(&self, other: &dyn Pane) -> bool { - self.y() == other.y() + other.rows() + 1 + self.y() == other.y() + other.rows() } fn is_directly_above(&self, other: &dyn Pane) -> bool { - self.y() + self.rows() + 1 == other.y() + self.y() + self.rows() == other.y() } fn horizontally_overlaps_with(&self, other: &dyn Pane) -> bool { - (self.y() >= other.y() && self.y() <= (other.y() + other.rows())) + (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()))) @@ -188,7 +211,7 @@ pub trait Pane { - std::cmp::max(self.y(), other.y()) } fn vertically_overlaps_with(&self, other: &dyn Pane) -> bool { - (self.x() >= other.x() && self.x() <= (other.x() + other.columns())) + (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() @@ -243,6 +266,21 @@ pub trait Pane { None => position.relative_to(&self.position_and_size()), } } + 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.rows() + } + 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.columns() + } + fn set_boundary_color(&mut self, _color: Option) {} + fn offset_content_columns(&mut self, _by: usize) {} + fn offset_content_rows(&mut self, _by: usize) {} + fn show_boundaries_frame(&mut self, _render_only_title: bool) {} + fn remove_boundaries_frame(&mut self) {} } impl Tab { @@ -252,7 +290,7 @@ impl Tab { index: usize, position: usize, name: String, - full_screen_ws: &PositionAndSize, + viewport: &PositionAndSize, os_api: Box, senders: ThreadSenders, max_panes: Option, @@ -260,9 +298,14 @@ impl Tab { mode_info: ModeInfo, colors: Palette, session_state: Arc>, + draw_pane_frames: bool, ) -> Self { let panes = if let Some(PaneId::Terminal(pid)) = pane_id { - let new_terminal = TerminalPane::new(pid, *full_screen_ws, colors); + let pane_title_only = true; + let mut new_terminal = TerminalPane::new(pid, *viewport, colors, 1); + if draw_pane_frames { + new_terminal.show_boundaries_frame(pane_title_only); + } os_api.set_terminal_size_using_fd( new_terminal.pid, new_terminal.columns() as u16, @@ -289,7 +332,8 @@ impl Tab { max_panes, panes_to_hide: HashSet::new(), active_terminal: pane_id, - full_screen_ws: *full_screen_ws, + viewport: *viewport, + display_area: *viewport, fullscreen_is_active: false, synchronize_is_active: false, os_api, @@ -298,21 +342,36 @@ impl Tab { mode_info, colors, session_state, + draw_pane_frames, } } pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec, tab_index: usize) { - // TODO: this should be an attribute on Screen instead of full_screen_ws + // TODO: this should be an attribute on Screen instead of viewport let free_space = PositionAndSize { x: 0, y: 0, - rows: self.full_screen_ws.rows, - cols: self.full_screen_ws.cols, + rows: self.viewport.rows, + cols: self.viewport.cols, ..Default::default() }; self.panes_to_hide.clear(); let positions_in_layout = layout.position_panes_in_space(&free_space); + + for (layout, position_and_size) in &positions_in_layout { + // we need to do this first because it decides the size of the screen + // which we use for other stuff in the main loop below (eg. which type of frames the + // panes should have) + if layout.borderless { + self.offset_viewport(position_and_size); + } + } + let mut positions_and_size = positions_in_layout.iter(); + let total_borderless_panes = layout.total_borderless_panes(); + let total_panes_with_border = positions_in_layout + .len() + .saturating_sub(total_borderless_panes); for (pane_kind, terminal_pane) in self.panes.iter_mut() { // for now the layout only supports terminal panes if let PaneId::Terminal(pid) = pane_kind { @@ -335,6 +394,7 @@ impl Tab { } } let mut new_pids = new_pids.iter(); + for (layout, position_and_size) in positions_and_size { // A plugin pane if let Some(Run::Plugin(Some(plugin))) = &layout.run { @@ -343,11 +403,18 @@ impl Tab { .send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone(), tab_index)) .unwrap(); let pid = pid_rx.recv().unwrap(); - let new_plugin = PluginPane::new( + let draw_pane_frames = self.draw_pane_frames && !layout.borderless; + let pane_title_only = !layout.borderless && total_panes_with_border == 1; + let title = String::from(plugin.as_path().as_os_str().to_string_lossy()); + let mut new_plugin = PluginPane::new( pid, *position_and_size, self.senders.to_plugin.as_ref().unwrap().clone(), + title, ); + if draw_pane_frames && !layout.borderless { + new_plugin.show_boundaries_frame(pane_title_only); + } self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin)); // Send an initial mode update to the newly loaded plugin only! self.senders @@ -359,11 +426,28 @@ impl Tab { } else { // there are still panes left to fill, use the pids we received in this method let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout - let new_terminal = TerminalPane::new(*pid, *position_and_size, self.colors); + let next_selectable_pane_position = self.get_next_selectable_pane_position(); + let pane_title_only = + next_selectable_pane_position == 1 && total_panes_with_border == 1; + let draw_pane_frames = self.draw_pane_frames && !layout.borderless; + let mut new_terminal = TerminalPane::new( + *pid, + *position_and_size, + self.colors, + next_selectable_pane_position, + ); + if draw_pane_frames { + new_terminal.show_boundaries_frame(pane_title_only); + } else { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(position_and_size, &self.viewport); + new_terminal.offset_content_columns(pane_columns_offset); + new_terminal.offset_content_rows(pane_rows_offset); + } self.os_api.set_terminal_size_using_fd( new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, + new_terminal.get_content_columns() as u16, + new_terminal.get_content_rows() as u16, ); self.panes .insert(PaneId::Terminal(*pid), Box::new(new_terminal)); @@ -387,7 +471,17 @@ impl Tab { } if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { - let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws, self.colors); + let next_selectable_pane_position = self.get_next_selectable_pane_position(); + let pane_title_only = next_selectable_pane_position == 1; + let mut new_terminal = TerminalPane::new( + term_pid, + self.viewport, + self.colors, + next_selectable_pane_position, + ); + if self.draw_pane_frames { + new_terminal.show_boundaries_frame(pane_title_only); + } self.os_api.set_terminal_size_using_fd( new_terminal.pid, new_terminal.columns() as u16, @@ -424,6 +518,7 @@ impl Tab { return; // likely no terminal large enough to split } let terminal_id_to_split = terminal_id_to_split.unwrap(); + let next_selectable_pane_position = self.get_next_selectable_pane_position(); let terminal_to_split = self.panes.get_mut(&terminal_id_to_split).unwrap(); let terminal_ws = PositionAndSize { rows: terminal_to_split.rows(), @@ -436,40 +531,90 @@ impl Tab { && terminal_to_split.rows() > terminal_to_split.min_height() * 2 { if let PaneId::Terminal(term_pid) = pid { - let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws); - let new_terminal = TerminalPane::new(term_pid, bottom_winsize, self.colors); + let (top_winsize, bottom_winsize) = split_horizontally(&terminal_ws); + let pane_title_only = next_selectable_pane_position == 1; + let mut new_terminal = TerminalPane::new( + term_pid, + bottom_winsize, + self.colors, + next_selectable_pane_position, + ); + if self.draw_pane_frames { + new_terminal.show_boundaries_frame(pane_title_only); + } else { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&bottom_winsize, &self.viewport); + new_terminal.offset_content_columns(pane_columns_offset); + new_terminal.offset_content_rows(pane_rows_offset); + } self.os_api.set_terminal_size_using_fd( new_terminal.pid, - bottom_winsize.cols as u16, - bottom_winsize.rows as u16, + new_terminal.get_content_columns() as u16, + new_terminal.get_content_rows() as u16, ); + if self.draw_pane_frames { + let only_title = false; + terminal_to_split.show_boundaries_frame(only_title); + } else { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&top_winsize, &self.viewport); + terminal_to_split.offset_content_columns(pane_columns_offset); + terminal_to_split.offset_content_rows(pane_rows_offset); + } terminal_to_split.change_pos_and_size(&top_winsize); + let terminal_to_split_content_columns = terminal_to_split.get_content_columns(); + let terminal_to_split_content_rows = terminal_to_split.get_content_rows(); self.panes.insert(pid, Box::new(new_terminal)); if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split { self.os_api.set_terminal_size_using_fd( terminal_id_to_split, - top_winsize.cols as u16, - top_winsize.rows as u16, + terminal_to_split_content_columns as u16, + terminal_to_split_content_rows as u16, ); } self.active_terminal = Some(pid); } } else if terminal_to_split.columns() > terminal_to_split.min_width() * 2 { if let PaneId::Terminal(term_pid) = pid { - let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws); - let new_terminal = TerminalPane::new(term_pid, right_winsize, self.colors); + let (left_winsize, right_winsize) = split_vertically(&terminal_ws); + let pane_title_only = next_selectable_pane_position == 1; + let mut new_terminal = TerminalPane::new( + term_pid, + right_winsize, + self.colors, + next_selectable_pane_position, + ); + if self.draw_pane_frames { + new_terminal.show_boundaries_frame(pane_title_only); + } else { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&right_winsize, &self.viewport); + new_terminal.offset_content_columns(pane_columns_offset); + new_terminal.offset_content_rows(pane_rows_offset); + } self.os_api.set_terminal_size_using_fd( new_terminal.pid, - right_winsize.cols as u16, - right_winsize.rows as u16, + new_terminal.get_content_columns() as u16, + new_terminal.get_content_rows() as u16, ); + if self.draw_pane_frames { + let only_title = false; + terminal_to_split.show_boundaries_frame(only_title); + } else { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&left_winsize, &self.viewport); + terminal_to_split.offset_content_columns(pane_columns_offset); + terminal_to_split.offset_content_rows(pane_rows_offset); + } terminal_to_split.change_pos_and_size(&left_winsize); + let terminal_to_split_content_columns = terminal_to_split.get_content_columns(); + let terminal_to_split_content_rows = terminal_to_split.get_content_rows(); self.panes.insert(pid, Box::new(new_terminal)); if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split { self.os_api.set_terminal_size_using_fd( terminal_id_to_split, - left_winsize.cols as u16, - left_winsize.rows as u16, + terminal_to_split_content_columns as u16, + terminal_to_split_content_rows as u16, ); } } @@ -485,20 +630,29 @@ impl Tab { } if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { - let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws, self.colors); + let next_selectable_pane_position = self.get_next_selectable_pane_position(); + let pane_title_only = next_selectable_pane_position == 1; + let mut new_terminal = TerminalPane::new( + term_pid, + self.viewport, + self.colors, + next_selectable_pane_position, + ); + if self.draw_pane_frames { + new_terminal.show_boundaries_frame(pane_title_only); + } self.os_api.set_terminal_size_using_fd( new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, + new_terminal.get_content_columns() as u16, + new_terminal.get_content_rows() as u16, ); self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); } } else if let PaneId::Terminal(term_pid) = pid { - // TODO: check minimum size of active terminal let active_pane_id = &self.get_active_pane_id().unwrap(); let active_pane = self.panes.get_mut(active_pane_id).unwrap(); - if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 { + if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 { self.senders .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty .unwrap(); @@ -511,23 +665,50 @@ impl Tab { cols: active_pane.columns(), ..Default::default() }; - let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws); + let (top_winsize, bottom_winsize) = split_horizontally(&terminal_ws); + if self.draw_pane_frames { + let only_title = false; + active_pane.show_boundaries_frame(only_title); + } else { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&top_winsize, &self.viewport); + active_pane.offset_content_columns(pane_columns_offset); + active_pane.offset_content_rows(pane_rows_offset); + } active_pane.change_pos_and_size(&top_winsize); - let new_terminal = TerminalPane::new(term_pid, bottom_winsize, self.colors); + let active_pane_content_columns = active_pane.get_content_columns(); + let active_pane_content_rows = active_pane.get_content_rows(); + + let next_selectable_pane_position = self.get_next_selectable_pane_position(); + let pane_title_only = next_selectable_pane_position == 1; + let mut new_terminal = TerminalPane::new( + term_pid, + bottom_winsize, + self.colors, + next_selectable_pane_position, + ); + if self.draw_pane_frames { + new_terminal.show_boundaries_frame(pane_title_only); + } else { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&bottom_winsize, &self.viewport); + new_terminal.offset_content_columns(pane_columns_offset); + new_terminal.offset_content_rows(pane_rows_offset); + } self.os_api.set_terminal_size_using_fd( new_terminal.pid, - bottom_winsize.cols as u16, - bottom_winsize.rows as u16, + new_terminal.get_content_columns() as u16, + new_terminal.get_content_rows() as u16, ); self.panes.insert(pid, Box::new(new_terminal)); if let PaneId::Terminal(active_terminal_pid) = active_pane_id { self.os_api.set_terminal_size_using_fd( *active_terminal_pid, - top_winsize.cols as u16, - top_winsize.rows as u16, + active_pane_content_columns as u16, + active_pane_content_rows as u16, ); } @@ -542,11 +723,21 @@ impl Tab { } if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { - let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws, self.colors); + let next_selectable_pane_position = self.get_next_selectable_pane_position(); + let pane_title_only = next_selectable_pane_position == 1; + let mut new_terminal = TerminalPane::new( + term_pid, + self.viewport, + self.colors, + next_selectable_pane_position, + ); + if self.draw_pane_frames { + new_terminal.show_boundaries_frame(pane_title_only); + } self.os_api.set_terminal_size_using_fd( new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, + new_terminal.get_content_columns() as u16, + new_terminal.get_content_rows() as u16, ); self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); @@ -555,7 +746,7 @@ impl Tab { // TODO: check minimum size of active terminal let active_pane_id = &self.get_active_pane_id().unwrap(); let active_pane = self.panes.get_mut(active_pane_id).unwrap(); - if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 { + if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 { self.senders .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty .unwrap(); @@ -568,23 +759,50 @@ impl Tab { cols: active_pane.columns(), ..Default::default() }; - let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws); + let (left_winsize, right_winsize) = split_vertically(&terminal_ws); + if self.draw_pane_frames { + let only_title = false; + active_pane.show_boundaries_frame(only_title); + } else { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&left_winsize, &self.viewport); + active_pane.offset_content_columns(pane_columns_offset); + active_pane.offset_content_rows(pane_rows_offset); + } active_pane.change_pos_and_size(&left_winsize); - let new_terminal = TerminalPane::new(term_pid, right_winsize, self.colors); + let active_pane_content_columns = active_pane.get_content_columns(); + let active_pane_content_rows = active_pane.get_content_rows(); + + let next_selectable_pane_position = self.get_next_selectable_pane_position(); + let pane_title_only = next_selectable_pane_position == 1; + let mut new_terminal = TerminalPane::new( + term_pid, + right_winsize, + self.colors, + next_selectable_pane_position, + ); + if self.draw_pane_frames { + new_terminal.show_boundaries_frame(pane_title_only); + } else { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&right_winsize, &self.viewport); + new_terminal.offset_content_columns(pane_columns_offset); + new_terminal.offset_content_rows(pane_rows_offset); + } self.os_api.set_terminal_size_using_fd( new_terminal.pid, - right_winsize.cols as u16, - right_winsize.rows as u16, + new_terminal.get_content_columns() as u16, + new_terminal.get_content_rows() as u16, ); self.panes.insert(pid, Box::new(new_terminal)); if let PaneId::Terminal(active_terminal_pid) = active_pane_id { self.os_api.set_terminal_size_using_fd( *active_terminal_pid, - left_winsize.cols as u16, - left_winsize.rows as u16, + active_pane_content_columns as u16, + active_pane_content_rows as u16, ); } @@ -673,36 +891,51 @@ impl Tab { if let Some(active_pane_id) = self.get_active_pane_id() { if self.fullscreen_is_active { for terminal_id in self.panes_to_hide.iter() { - self.panes - .get_mut(terminal_id) - .unwrap() - .set_should_render(true); + let pane = self.panes.get_mut(terminal_id).unwrap(); + pane.set_should_render(true); + pane.set_should_render_boundaries(true); } self.panes_to_hide.clear(); + let selectable_pane_count = self.get_selectable_pane_count(); let active_terminal = self.panes.get_mut(&active_pane_id).unwrap(); + if selectable_pane_count > 1 && self.draw_pane_frames { + let only_title = false; + active_terminal.show_boundaries_frame(only_title); + } + if !self.draw_pane_frames { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&active_terminal.position_and_size(), &self.viewport); + active_terminal.offset_content_columns(pane_columns_offset); + active_terminal.offset_content_rows(pane_rows_offset); + } active_terminal.reset_size_and_position_override(); } else { let panes = self.get_panes(); - let pane_ids_to_hide = - panes.filter_map( - |(&id, _)| { - if id != active_pane_id { - Some(id) - } else { - None - } - }, - ); + let pane_ids_to_hide = panes.filter_map(|(&id, _pane)| { + if id != active_pane_id && self.is_inside_viewport(&id) { + Some(id) + } else { + None + } + }); self.panes_to_hide = pane_ids_to_hide.collect(); if self.panes_to_hide.is_empty() { // nothing to do, pane is already as fullscreen as it can be, let's bail return; } else { let active_terminal = self.panes.get_mut(&active_pane_id).unwrap(); + if self.draw_pane_frames { + // full screen panes don't need their full frame + let only_title = true; + active_terminal.show_boundaries_frame(only_title); + } else { + active_terminal.offset_content_rows(0); + active_terminal.offset_content_columns(0); + } active_terminal.override_size_and_position( - self.full_screen_ws.x, - self.full_screen_ws.y, - &self.full_screen_ws, + self.viewport.x, + self.viewport.y, + &self.viewport, ); } } @@ -710,8 +943,8 @@ impl Tab { if let PaneId::Terminal(active_pid) = active_pane_id { self.os_api.set_terminal_size_using_fd( active_pid, - active_terminal.columns() as u16, - active_terminal.rows() as u16, + active_terminal.get_content_columns() as u16, + active_terminal.get_content_rows() as u16, ); } self.set_force_render(); @@ -725,6 +958,7 @@ impl Tab { pub fn set_force_render(&mut self) { for pane in self.panes.values_mut() { pane.set_should_render(true); + pane.set_should_render_boundaries(true); pane.render_full_viewport(); } } @@ -734,6 +968,48 @@ impl Tab { pub fn toggle_sync_panes_is_active(&mut self) { self.synchronize_is_active = !self.synchronize_is_active; } + pub fn mark_active_pane_for_rerender(&mut self) { + if let Some(active_terminal) = self + .active_terminal + .and_then(|active_terminal_id| self.panes.get_mut(&active_terminal_id)) + { + active_terminal.set_should_render(true) + } + // .and_then(|active_terminal_id| self.panes.get_mut(&active_terminal_id)) { + // active_terminal.set_should_render(true) + // } + } + pub fn set_pane_frames(&mut self, draw_pane_frames: bool) { + self.draw_pane_frames = draw_pane_frames; + let selectable_pane_count = self.panes.iter().filter(|(_, p)| p.selectable()).count(); + for (pane_id, pane) in self.panes.iter_mut() { + if draw_pane_frames { + let should_render_only_title = (selectable_pane_count == 1 + && self.active_terminal == Some(*pane_id)) + || (self.fullscreen_is_active && self.active_terminal == Some(*pane_id)); + pane.offset_content_rows(0); + pane.offset_content_columns(0); + pane.show_boundaries_frame(should_render_only_title); + } else { + let position_and_size = pane + .position_and_size_override() + .unwrap_or_else(|| pane.position_and_size()); + pane.remove_boundaries_frame(); + + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&position_and_size, &self.viewport); + pane.offset_content_columns(pane_columns_offset); + pane.offset_content_rows(pane_rows_offset); + } + if let PaneId::Terminal(pid) = pane_id { + self.os_api.set_terminal_size_using_fd( + *pid, + pane.get_content_columns() as u16, + pane.get_content_rows() as u16, + ); + } + } + } pub fn render(&mut self) { if self.active_terminal.is_none() || *self.session_state.read().unwrap() != SessionState::Attached @@ -744,10 +1020,7 @@ impl Tab { return; } let mut output = String::new(); - let mut boundaries = Boundaries::new( - self.full_screen_ws.cols as u16, - self.full_screen_ws.rows as u16, - ); + let mut boundaries = Boundaries::new(&self.viewport); let hide_cursor = "\u{1b}[?25l"; output.push_str(hide_cursor); if self.should_clear_display_before_rendering { @@ -755,21 +1028,35 @@ impl Tab { output.push_str(clear_display); self.should_clear_display_before_rendering = false; } - for (kind, pane) in self.panes.iter_mut() { + for (_kind, pane) in self.panes.iter_mut() { if !self.panes_to_hide.contains(&pane.pid()) { match self.active_terminal.unwrap() == pane.pid() { true => { pane.set_active_at(Instant::now()); - boundaries.add_rect(pane.as_ref(), self.mode_info.mode, Some(self.colors)) + match self.mode_info.mode { + InputMode::Normal | InputMode::Locked => { + pane.set_boundary_color(Some(self.colors.green)); + } + _ => { + pane.set_boundary_color(Some(self.colors.orange)); + } + } + if !self.draw_pane_frames { + boundaries.add_rect( + pane.as_ref(), + self.mode_info.mode, + Some(self.colors), + ) + } + } + false => { + pane.set_boundary_color(None); + if !pane.invisible_borders() && !self.draw_pane_frames { + boundaries.add_rect(pane.as_ref(), self.mode_info.mode, None); + } } - false => boundaries.add_rect(pane.as_ref(), self.mode_info.mode, None), } if let Some(vte_output) = pane.render() { - let vte_output = if let PaneId::Terminal(_) = kind { - vte_output - } else { - adjust_to_size(&vte_output, pane.rows(), pane.columns()) - }; // FIXME: Use Termion for cursor and style clearing? output.push_str(&format!( "\u{1b}[{};{}H\u{1b}[m{}", @@ -781,8 +1068,9 @@ impl Tab { } } - // TODO: only render (and calculate) boundaries if there was a resize - output.push_str(&boundaries.vte_output()); + if !self.draw_pane_frames { + output.push_str(&boundaries.vte_output()); + } match self.get_active_terminal_cursor_position() { Some((cursor_position_x, cursor_position_y)) => { @@ -814,6 +1102,28 @@ impl Tab { fn get_selectable_panes(&self) -> impl Iterator)> { self.panes.iter().filter(|(_, p)| p.selectable()) } + fn get_selectable_pane_count(&self) -> usize { + self.get_selectable_panes().count() + } + fn get_next_selectable_pane_position(&self) -> usize { + self.panes + .iter() + .filter(|(k, _)| match k { + PaneId::Plugin(_) => false, + PaneId::Terminal(_) => true, + }) + .count() + + 1 + } + fn is_the_only_selectable_pane(&self, pane_id: &PaneId) -> bool { + let selectable_panes = self.get_selectable_panes(); + if selectable_panes.count() == 1 { + let pane = self.panes.get(pane_id); + pane.map(|pane| pane.selectable()).unwrap_or(false) + } else { + false + } + } fn has_panes(&self) -> bool { let mut all_terminals = self.get_panes(); all_terminals.next().is_some() @@ -822,11 +1132,12 @@ impl Tab { let mut all_terminals = self.get_selectable_panes(); all_terminals.next().is_some() } - fn next_active_pane(&self, panes: Vec) -> Option { + fn next_active_pane(&self, panes: &[PaneId]) -> Option { panes - .into_iter() + .iter() .rev() .find(|pid| self.panes.get(pid).unwrap().selectable()) + .copied() } fn pane_ids_directly_left_of(&self, id: &PaneId) -> Option> { let mut ids = vec![]; @@ -835,7 +1146,7 @@ impl Tab { return None; } for (&pid, terminal) in self.get_panes() { - if terminal.x() + terminal.columns() == terminal_to_check.x() - 1 { + if terminal.x() + terminal.columns() == terminal_to_check.x() { ids.push(pid); } } @@ -849,7 +1160,7 @@ impl Tab { let mut ids = vec![]; let terminal_to_check = self.panes.get(id).unwrap(); for (&pid, terminal) in self.get_panes() { - if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() + 1 { + if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() { ids.push(pid); } } @@ -863,7 +1174,7 @@ impl Tab { let mut ids = vec![]; let terminal_to_check = self.panes.get(id).unwrap(); for (&pid, terminal) in self.get_panes() { - if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() + 1 { + if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() { ids.push(pid); } } @@ -877,7 +1188,7 @@ impl Tab { let mut ids = vec![]; let terminal_to_check = self.panes.get(id).unwrap(); for (&pid, terminal) in self.get_panes() { - if terminal.y() + terminal.rows() + 1 == terminal_to_check.y() { + if terminal.y() + terminal.rows() == terminal_to_check.y() { ids.push(pid); } } @@ -937,7 +1248,7 @@ impl Tab { right_aligned_terminals.sort_by_key(|a| Reverse(a.y())); for terminal in right_aligned_terminals { let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.y() + terminal.rows() + 1 == terminal_to_check.y() { + if terminal.y() + terminal.rows() == terminal_to_check.y() { terminals.push(terminal); } } @@ -946,11 +1257,11 @@ impl Tab { for terminal in &terminals { let bottom_terminal_boundary = terminal.y() + terminal.rows(); if terminal_borders_to_the_right - .get(&(bottom_terminal_boundary + 1)) + .get(&bottom_terminal_boundary) .is_some() && top_resize_border < bottom_terminal_boundary { - top_resize_border = bottom_terminal_boundary + 1; + top_resize_border = bottom_terminal_boundary; } } terminals.retain(|terminal| terminal.y() >= top_resize_border); @@ -980,12 +1291,12 @@ impl Tab { right_aligned_terminals.sort_by_key(|a| a.y()); for terminal in right_aligned_terminals { let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() + 1 { + if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() { terminals.push(terminal); } } // bottom-most border aligned with a pane border to the right - let mut bottom_resize_border = self.full_screen_ws.rows; + let mut bottom_resize_border = self.viewport.y + self.viewport.rows; for terminal in &terminals { let top_terminal_boundary = terminal.y(); if terminal_borders_to_the_right @@ -1023,7 +1334,7 @@ impl Tab { left_aligned_terminals.sort_by_key(|a| Reverse(a.y())); for terminal in left_aligned_terminals { let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.y() + terminal.rows() + 1 == terminal_to_check.y() { + if terminal.y() + terminal.rows() == terminal_to_check.y() { terminals.push(terminal); } } @@ -1032,11 +1343,11 @@ impl Tab { for terminal in &terminals { let bottom_terminal_boundary = terminal.y() + terminal.rows(); if terminal_borders_to_the_left - .get(&(bottom_terminal_boundary + 1)) + .get(&bottom_terminal_boundary) .is_some() && top_resize_border < bottom_terminal_boundary { - top_resize_border = bottom_terminal_boundary + 1; + top_resize_border = bottom_terminal_boundary; } } terminals.retain(|terminal| terminal.y() >= top_resize_border); @@ -1066,12 +1377,12 @@ impl Tab { left_aligned_terminals.sort_by_key(|a| a.y()); for terminal in left_aligned_terminals { let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() + 1 { + if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() { terminals.push(terminal); } } // bottom-most border aligned with a pane border to the left - let mut bottom_resize_border = self.full_screen_ws.rows; + let mut bottom_resize_border = self.viewport.y + self.viewport.rows; for terminal in &terminals { let top_terminal_boundary = terminal.y(); if terminal_borders_to_the_left @@ -1112,7 +1423,7 @@ impl Tab { top_aligned_terminals.sort_by_key(|a| Reverse(a.x())); for terminal in top_aligned_terminals { let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.x() + terminal.columns() + 1 == terminal_to_check.x() { + if terminal.x() + terminal.columns() == terminal_to_check.x() { terminals.push(terminal); } } @@ -1121,11 +1432,11 @@ impl Tab { for terminal in &terminals { let right_terminal_boundary = terminal.x() + terminal.columns(); if terminal_borders_above - .get(&(right_terminal_boundary + 1)) + .get(&right_terminal_boundary) .is_some() && left_resize_border < right_terminal_boundary { - left_resize_border = right_terminal_boundary + 1; + left_resize_border = right_terminal_boundary; } } terminals.retain(|terminal| terminal.x() >= left_resize_border); @@ -1151,12 +1462,12 @@ impl Tab { top_aligned_terminals.sort_by_key(|a| a.x()); for terminal in top_aligned_terminals { let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() + 1 { + if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() { terminals.push(terminal); } } // rightmost border aligned with a pane border above - let mut right_resize_border = self.full_screen_ws.cols; + let mut right_resize_border = self.viewport.x + self.viewport.cols; for terminal in &terminals { let left_terminal_boundary = terminal.x(); if terminal_borders_above @@ -1190,7 +1501,7 @@ impl Tab { // terminals that are next to each other up to current for terminal in bottom_aligned_terminals { let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.x() + terminal.columns() + 1 == terminal_to_check.x() { + if terminal.x() + terminal.columns() == terminal_to_check.x() { terminals.push(terminal); } } @@ -1199,11 +1510,11 @@ impl Tab { for terminal in &terminals { let right_terminal_boundary = terminal.x() + terminal.columns(); if terminal_borders_below - .get(&(right_terminal_boundary + 1)) + .get(&right_terminal_boundary) .is_some() && left_resize_border < right_terminal_boundary { - left_resize_border = right_terminal_boundary + 1; + left_resize_border = right_terminal_boundary; } } terminals.retain(|terminal| terminal.x() >= left_resize_border); @@ -1229,12 +1540,12 @@ impl Tab { // terminals that are next to each other up to current for terminal in bottom_aligned_terminals { let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() + 1 { + if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() { terminals.push(terminal); } } // leftmost border aligned with a pane border above - let mut right_resize_border = self.full_screen_ws.cols; + let mut right_resize_border = self.viewport.x + self.viewport.cols; for terminal in &terminals { let left_terminal_boundary = terminal.x(); if terminal_borders_below @@ -1246,6 +1557,7 @@ impl Tab { } } terminals.retain(|terminal| terminal.x() + terminal.columns() <= right_resize_border); + let right_resize_border = if terminals.is_empty() { terminal_to_check.x() + terminal_to_check.columns() } else { @@ -1257,88 +1569,145 @@ impl Tab { fn reduce_pane_height_down(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_height_down(count); + let position_and_size = terminal.position_and_size(); + + if !self.draw_pane_frames { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&position_and_size, &self.viewport); + terminal.offset_content_columns(pane_columns_offset); + terminal.offset_content_rows(pane_rows_offset); + } if let PaneId::Terminal(pid) = id { self.os_api.set_terminal_size_using_fd( *pid, - terminal.columns() as u16, - terminal.rows() as u16, + terminal.get_content_columns() as u16, + terminal.get_content_rows() as u16, ); } } fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_height_up(count); + let position_and_size = terminal.position_and_size(); + if !self.draw_pane_frames { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&position_and_size, &self.viewport); + terminal.offset_content_columns(pane_columns_offset); + terminal.offset_content_rows(pane_rows_offset); + } if let PaneId::Terminal(pid) = id { self.os_api.set_terminal_size_using_fd( *pid, - terminal.columns() as u16, - terminal.rows() as u16, + terminal.get_content_columns() as u16, + terminal.get_content_rows() as u16, ); } } fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_height_down(count); + let position_and_size = terminal.position_and_size(); + if !self.draw_pane_frames { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&position_and_size, &self.viewport); + terminal.offset_content_columns(pane_columns_offset); + terminal.offset_content_rows(pane_rows_offset); + } if let PaneId::Terminal(pid) = terminal.pid() { self.os_api.set_terminal_size_using_fd( pid, - terminal.columns() as u16, - terminal.rows() as u16, + terminal.get_content_columns() as u16, + terminal.get_content_rows() as u16, ); } } fn increase_pane_height_up(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_height_up(count); + let position_and_size = terminal.position_and_size(); + if !self.draw_pane_frames { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&position_and_size, &self.viewport); + terminal.offset_content_columns(pane_columns_offset); + terminal.offset_content_rows(pane_rows_offset); + } if let PaneId::Terminal(pid) = terminal.pid() { self.os_api.set_terminal_size_using_fd( pid, - terminal.columns() as u16, - terminal.rows() as u16, + terminal.get_content_columns() as u16, + terminal.get_content_rows() as u16, ); } } fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_width_right(count); + let position_and_size = terminal.position_and_size(); + if !self.draw_pane_frames { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&position_and_size, &self.viewport); + terminal.offset_content_columns(pane_columns_offset); + terminal.offset_content_rows(pane_rows_offset); + } if let PaneId::Terminal(pid) = terminal.pid() { self.os_api.set_terminal_size_using_fd( pid, - terminal.columns() as u16, - terminal.rows() as u16, + terminal.get_content_columns() as u16, + terminal.get_content_rows() as u16, ); } } fn increase_pane_width_left(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_width_left(count); + let position_and_size = terminal.position_and_size(); + if !self.draw_pane_frames { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&position_and_size, &self.viewport); + terminal.offset_content_columns(pane_columns_offset); + terminal.offset_content_rows(pane_rows_offset); + } if let PaneId::Terminal(pid) = terminal.pid() { self.os_api.set_terminal_size_using_fd( pid, - terminal.columns() as u16, - terminal.rows() as u16, + terminal.get_content_columns() as u16, + terminal.get_content_rows() as u16, ); } } fn reduce_pane_width_right(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_width_right(count); + let position_and_size = terminal.position_and_size(); + if !self.draw_pane_frames { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&position_and_size, &self.viewport); + terminal.offset_content_columns(pane_columns_offset); + terminal.offset_content_rows(pane_rows_offset); + } if let PaneId::Terminal(pid) = terminal.pid() { self.os_api.set_terminal_size_using_fd( pid, - terminal.columns() as u16, - terminal.rows() as u16, + terminal.get_content_columns() as u16, + terminal.get_content_rows() as u16, ); } } fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_width_left(count); + let position_and_size = terminal.position_and_size(); + if !self.draw_pane_frames { + let (pane_columns_offset, pane_rows_offset) = + pane_content_offset(&position_and_size, &self.viewport); + terminal.offset_content_columns(pane_columns_offset); + terminal.offset_content_rows(pane_rows_offset); + } if let PaneId::Terminal(pid) = terminal.pid() { self.os_api.set_terminal_size_using_fd( pid, - terminal.columns() as u16, - terminal.rows() as u16, + terminal.get_content_columns() as u16, + terminal.get_content_rows() as u16, ); } } @@ -1381,6 +1750,18 @@ impl Tab { terminals_below.retain(|t| { self.pane_is_between_vertical_borders(t, left_resize_border, right_resize_border) }); + + for terminal_id in terminals_to_the_left + .iter() + .chain(terminals_to_the_right.iter()) + { + let pane = self.panes.get(terminal_id).unwrap(); + if (pane.rows() as isize) - (count as isize) < pane.min_height() as isize { + // dirty, dirty hack - should be fixed by the resizing overhaul + return; + } + } + self.reduce_pane_height_up(id, count); for terminal_id in terminals_below { self.increase_pane_height_up(&terminal_id, count); @@ -1407,6 +1788,18 @@ impl Tab { terminals_above.retain(|t| { self.pane_is_between_vertical_borders(t, left_resize_border, right_resize_border) }); + + for terminal_id in terminals_to_the_left + .iter() + .chain(terminals_to_the_right.iter()) + { + let pane = self.panes.get(terminal_id).unwrap(); + if (pane.rows() as isize) - (count as isize) < pane.min_height() as isize { + // dirty, dirty hack - should be fixed by the resizing overhaul + return; + } + } + self.reduce_pane_height_down(id, count); for terminal_id in terminals_above { self.increase_pane_height_down(&terminal_id, count); @@ -1433,6 +1826,15 @@ impl Tab { terminals_to_the_left.retain(|t| { self.pane_is_between_horizontal_borders(t, top_resize_border, bottom_resize_border) }); + + for terminal_id in terminals_above.iter().chain(terminals_below.iter()) { + let pane = self.panes.get(terminal_id).unwrap(); + if (pane.columns() as isize) - (count as isize) < pane.min_width() as isize { + // dirty, dirty hack - should be fixed by the resizing overhaul + return; + } + } + self.reduce_pane_width_right(id, count); for terminal_id in terminals_to_the_left { self.increase_pane_width_right(&terminal_id, count); @@ -1456,6 +1858,15 @@ impl Tab { terminals_to_the_right.retain(|t| { self.pane_is_between_horizontal_borders(t, top_resize_border, bottom_resize_border) }); + + for terminal_id in terminals_above.iter().chain(terminals_below.iter()) { + let pane = self.panes.get(terminal_id).unwrap(); + if (pane.columns() as isize) - (count as isize) < pane.min_width() as isize { + // dirty, dirty hack - should be fixed by the resizing overhaul + return; + } + } + self.reduce_pane_width_left(id, count); for terminal_id in terminals_to_the_right { self.increase_pane_width_left(&terminal_id, count); @@ -1522,7 +1933,9 @@ impl Tab { .expect("can't increase pane size right if there are no terminals to the right"); let terminal_borders_to_the_right: HashSet = terminals_to_the_right .iter() - .map(|t| self.panes.get(t).unwrap().y()) + .map(|t| { + return self.panes.get(t).unwrap().y(); + }) .collect(); let (top_resize_border, terminals_above) = self.right_aligned_contiguous_panes_above(id, &terminal_borders_to_the_right); @@ -1741,13 +2154,14 @@ impl Tab { } if let Some((column_difference, row_difference)) = PaneResizer::new(&mut self.panes, &mut self.os_api) - .resize(self.full_screen_ws, new_screen_size) + .resize(self.display_area, new_screen_size) { self.should_clear_display_before_rendering = true; - self.full_screen_ws.cols = - (self.full_screen_ws.cols as isize + column_difference) as usize; - self.full_screen_ws.rows = - (self.full_screen_ws.rows as isize + row_difference) as usize; + + self.viewport.cols = (self.viewport.cols as isize + column_difference) as usize; + self.viewport.rows = (self.viewport.rows as isize + row_difference) as usize; + self.display_area.cols = (self.display_area.cols as isize + column_difference) as usize; + self.display_area.rows = (self.display_area.rows as isize + row_difference) as usize; }; } pub fn resize_left(&mut self) { @@ -1895,6 +2309,14 @@ impl Tab { .map(|(_, (pid, _))| pid); match next_index { Some(&p) => { + // render previously active pane so that its frame does not remain actively + // colored + let previously_active_pane = + self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + previously_active_pane.set_should_render(true); + let next_active_pane = self.panes.get_mut(&p).unwrap(); + next_active_pane.set_should_render(true); + self.active_terminal = Some(p); self.render(); return true; @@ -1927,6 +2349,14 @@ impl Tab { .map(|(_, (pid, _))| pid); match next_index { Some(&p) => { + // render previously active pane so that its frame does not remain actively + // colored + let previously_active_pane = + self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + previously_active_pane.set_should_render(true); + let next_active_pane = self.panes.get_mut(&p).unwrap(); + next_active_pane.set_should_render(true); + self.active_terminal = Some(p); } None => { @@ -1957,6 +2387,14 @@ impl Tab { .map(|(_, (pid, _))| pid); match next_index { Some(&p) => { + // render previously active pane so that its frame does not remain actively + // colored + let previously_active_pane = + self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + previously_active_pane.set_should_render(true); + let next_active_pane = self.panes.get_mut(&p).unwrap(); + next_active_pane.set_should_render(true); + self.active_terminal = Some(p); } None => { @@ -1988,6 +2426,14 @@ impl Tab { .map(|(_, (pid, _))| pid); match next_index { Some(&p) => { + // render previously active pane so that its frame does not remain actively + // colored + let previously_active_pane = + self.panes.get_mut(&self.active_terminal.unwrap()).unwrap(); + previously_active_pane.set_should_render(true); + let next_active_pane = self.panes.get_mut(&p).unwrap(); + next_active_pane.set_should_render(true); + self.active_terminal = Some(p); self.render(); return true; @@ -2128,7 +2574,7 @@ impl Tab { if let Some(pane) = self.panes.get_mut(&id) { pane.set_selectable(selectable); if self.get_active_pane_id() == Some(id) && !selectable { - self.active_terminal = self.next_active_pane(self.get_pane_ids()) + self.active_terminal = self.next_active_pane(&self.get_pane_ids()) } } } @@ -2162,15 +2608,26 @@ impl Tab { if let Some(panes) = self.panes_to_the_left_between_aligning_borders(id) { if panes.iter().all(|p| { let pane = self.panes.get(p).unwrap(); - pane.can_increase_width_by(pane_to_close_width + 1) + pane.can_increase_width_by(pane_to_close_width) }) { - for pane_id in panes.iter() { - self.increase_pane_width_right(pane_id, pane_to_close_width + 1); - // 1 for the border - } self.panes.remove(&id); if self.active_terminal == Some(id) { - self.active_terminal = self.next_active_pane(panes); + let next_active_pane = self.next_active_pane(&panes); + self.active_terminal = next_active_pane; + if let Some(next_active_pane) = next_active_pane { + if self.is_the_only_selectable_pane(&next_active_pane) + && self.draw_pane_frames + { + let should_render_only_title = true; + self.panes + .get_mut(&next_active_pane) + .unwrap() + .show_boundaries_frame(should_render_only_title); + } + } + } + for pane_id in panes.iter() { + self.increase_pane_width_right(pane_id, pane_to_close_width); } return; } @@ -2178,15 +2635,26 @@ impl Tab { if let Some(panes) = self.panes_to_the_right_between_aligning_borders(id) { if panes.iter().all(|p| { let pane = self.panes.get(p).unwrap(); - pane.can_increase_width_by(pane_to_close_width + 1) + pane.can_increase_width_by(pane_to_close_width) }) { - for pane_id in panes.iter() { - self.increase_pane_width_left(pane_id, pane_to_close_width + 1); - // 1 for the border - } self.panes.remove(&id); if self.active_terminal == Some(id) { - self.active_terminal = self.next_active_pane(panes); + let next_active_pane = self.next_active_pane(&panes); + self.active_terminal = next_active_pane; + if let Some(next_active_pane) = next_active_pane { + if self.is_the_only_selectable_pane(&next_active_pane) + && self.draw_pane_frames + { + let should_render_only_title = true; + self.panes + .get_mut(&next_active_pane) + .unwrap() + .show_boundaries_frame(should_render_only_title); + } + } + } + for pane_id in panes.iter() { + self.increase_pane_width_left(pane_id, pane_to_close_width); } return; } @@ -2194,15 +2662,26 @@ impl Tab { if let Some(panes) = self.panes_above_between_aligning_borders(id) { if panes.iter().all(|p| { let pane = self.panes.get(p).unwrap(); - pane.can_increase_height_by(pane_to_close_height + 1) + pane.can_increase_height_by(pane_to_close_height) }) { - for pane_id in panes.iter() { - self.increase_pane_height_down(pane_id, pane_to_close_height + 1); - // 1 for the border - } self.panes.remove(&id); if self.active_terminal == Some(id) { - self.active_terminal = self.next_active_pane(panes); + let next_active_pane = self.next_active_pane(&panes); + self.active_terminal = next_active_pane; + if let Some(next_active_pane) = next_active_pane { + if self.is_the_only_selectable_pane(&next_active_pane) + && self.draw_pane_frames + { + let should_render_only_title = true; + self.panes + .get_mut(&next_active_pane) + .unwrap() + .show_boundaries_frame(should_render_only_title); + } + } + } + for pane_id in panes.iter() { + self.increase_pane_height_down(pane_id, pane_to_close_height); } return; } @@ -2210,15 +2689,26 @@ impl Tab { if let Some(panes) = self.panes_below_between_aligning_borders(id) { if panes.iter().all(|p| { let pane = self.panes.get(p).unwrap(); - pane.can_increase_height_by(pane_to_close_height + 1) + pane.can_increase_height_by(pane_to_close_height) }) { - for pane_id in panes.iter() { - self.increase_pane_height_up(pane_id, pane_to_close_height + 1); - // 1 for the border - } self.panes.remove(&id); if self.active_terminal == Some(id) { - self.active_terminal = self.next_active_pane(panes); + let next_active_pane = self.next_active_pane(&panes); + self.active_terminal = next_active_pane; + if let Some(next_active_pane) = next_active_pane { + if self.is_the_only_selectable_pane(&next_active_pane) + && self.draw_pane_frames + { + let should_render_only_title = true; + self.panes + .get_mut(&next_active_pane) + .unwrap() + .show_boundaries_frame(should_render_only_title); + } + } + } + for pane_id in panes.iter() { + self.increase_pane_height_up(pane_id, pane_to_close_height); } return; } @@ -2367,10 +2857,10 @@ impl Tab { self.write_selection_to_clipboard(&selected_text); } } - pub fn handle_mouse_hold(&mut self, position: &Position) { + pub fn handle_mouse_hold(&mut self, position_on_screen: &Position) { if let Some(active_pane_id) = self.get_active_pane_id() { if let Some(active_pane) = self.panes.get_mut(&active_pane_id) { - let relative_position = active_pane.relative_position(position); + let relative_position = active_pane.relative_position(position_on_screen); active_pane.update_selection(&relative_position); } } @@ -2381,6 +2871,9 @@ impl Tab { let selected_text = self.get_active_pane().and_then(|p| p.get_selected_text()); if let Some(selected_text) = selected_text { self.write_selection_to_clipboard(&selected_text); + self.senders + .send_to_plugin(PluginInstruction::Update(None, Event::CopyToClipboard)) + .unwrap(); } } @@ -2389,6 +2882,41 @@ impl Tab { self.senders .send_to_server(ServerInstruction::Render(Some(output))) .unwrap(); + self.senders + .send_to_plugin(PluginInstruction::Update(None, Event::CopyToClipboard)) + .unwrap(); + } + fn is_inside_viewport(&self, pane_id: &PaneId) -> bool { + let pane_position_and_size = self.panes.get(pane_id).unwrap().position_and_size(); + pane_position_and_size.y >= self.viewport.y + && pane_position_and_size.y + pane_position_and_size.rows + <= self.viewport.y + self.viewport.rows + } + fn offset_viewport(&mut self, position_and_size: &PositionAndSize) { + if position_and_size.x == self.viewport.x + && position_and_size.x + position_and_size.cols == self.viewport.x + self.viewport.cols + { + if position_and_size.y == self.viewport.y { + self.viewport.y += position_and_size.rows; + self.viewport.rows -= position_and_size.rows; + } else if position_and_size.y + position_and_size.rows + == self.viewport.y + self.viewport.rows + { + self.viewport.rows -= position_and_size.rows; + } + } + if position_and_size.y == self.viewport.y + && position_and_size.y + position_and_size.rows == self.viewport.y + self.viewport.rows + { + if position_and_size.x == self.viewport.x { + self.viewport.x += position_and_size.cols; + self.viewport.cols -= position_and_size.cols; + } else if position_and_size.x + position_and_size.cols + == self.viewport.x + self.viewport.cols + { + self.viewport.cols -= position_and_size.cols; + } + } } } diff --git a/zellij-server/src/ui/boundaries.rs b/zellij-server/src/ui/boundaries.rs index 42a80ace10..b25f1cc082 100644 --- a/zellij-server/src/ui/boundaries.rs +++ b/zellij-server/src/ui/boundaries.rs @@ -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, + position_and_size: PositionAndSize, boundary_characters: HashMap, } 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) { + 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 } } diff --git a/zellij-server/src/ui/mod.rs b/zellij-server/src/ui/mod.rs index 0087e54e85..beb70bf042 100644 --- a/zellij-server/src/ui/mod.rs +++ b/zellij-server/src/ui/mod.rs @@ -1,3 +1,4 @@ pub mod boundaries; +pub mod pane_boundaries_frame; pub mod pane_resizer; pub mod pane_resizer_beta; diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs new file mode 100644 index 0000000000..0ae454d96b --- /dev/null +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -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) -> 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, + 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) { + 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 { + 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 { + 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 { + 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) + } +} diff --git a/zellij-server/src/ui/pane_resizer.rs b/zellij-server/src/ui/pane_resizer.rs index 39f2d40c6d..1b5ddabf3d 100644 --- a/zellij-server/src/ui/pane_resizer.rs +++ b/zellij-server/src/ui/pane_resizer.rs @@ -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 { 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 { 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 { 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 { 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; } }; diff --git a/zellij-server/src/ui/title_telescope.rs b/zellij-server/src/ui/title_telescope.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index df159dde00..6248ded271 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -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] @@ -195,7 +202,7 @@ pub fn close_the_middle_tab() { assert_eq!(screen.tabs.len(), 2, "Two tabs left"); assert_eq!( screen.get_active_tab().unwrap().position, - 0, + 1, "Active tab switched to previous tab" ); } @@ -247,3 +254,186 @@ fn move_focus_right_at_right_screen_edge_changes_tab() { "Active tab switched to next" ); } + +#[test] +pub fn toggle_to_previous_tab_simple() { + let position_and_size = PositionAndSize { + cols: 121, + rows: 20, + x: 0, + y: 0, + ..Default::default() + }; + let mut screen = create_new_screen(position_and_size); + + screen.new_tab(1); + screen.new_tab(2); + screen.go_to_tab(1); + screen.go_to_tab(2); + + screen.toggle_tab(); + assert_eq!( + screen.get_active_tab().unwrap().position, + 0, + "Active tab toggler to previous tab" + ); + + screen.toggle_tab(); + assert_eq!( + screen.get_active_tab().unwrap().position, + 1, + "Active tab toggler to previous tab" + ); +} + +#[test] +pub fn toggle_to_previous_tab_create_tabs_only() { + let position_and_size = PositionAndSize { + cols: 121, + rows: 20, + x: 0, + y: 0, + ..Default::default() + }; + let mut screen = create_new_screen(position_and_size); + + screen.new_tab(1); + screen.new_tab(2); + screen.new_tab(3); + + assert_eq!( + screen.tab_history, + vec![None, Some(0), Some(1)], + "Tab history is invalid" + ); + + screen.toggle_tab(); + assert_eq!( + screen.get_active_tab().unwrap().position, + 1, + "Active tab toggler to previous tab" + ); + assert_eq!( + screen.tab_history, + vec![None, Some(0), Some(2)], + "Tab history is invalid" + ); + + screen.toggle_tab(); + assert_eq!( + screen.get_active_tab().unwrap().position, + 2, + "Active tab toggler to previous tab" + ); + assert_eq!( + screen.tab_history, + vec![None, Some(0), Some(1)], + "Tab history is invalid" + ); + + screen.toggle_tab(); + assert_eq!( + screen.get_active_tab().unwrap().position, + 1, + "Active tab toggler to previous tab" + ); +} + +#[test] +pub fn toggle_to_previous_tab_delete() { + let position_and_size = PositionAndSize { + cols: 121, + rows: 20, + x: 0, + y: 0, + ..Default::default() + }; + let mut screen = create_new_screen(position_and_size); + + screen.new_tab(1); // 0 + screen.new_tab(2); // 1 + screen.new_tab(3); // 2 + screen.new_tab(4); // 3 + + assert_eq!( + screen.tab_history, + vec![None, Some(0), Some(1), Some(2)], + "Tab history is invalid" + ); + assert_eq!( + screen.get_active_tab().unwrap().position, + 3, + "Active tab toggler to previous tab" + ); + + screen.toggle_tab(); + assert_eq!( + screen.tab_history, + vec![None, Some(0), Some(1), Some(3)], + "Tab history is invalid" + ); + assert_eq!( + screen.get_active_tab().unwrap().position, + 2, + "Active tab toggler to previous tab" + ); + + screen.toggle_tab(); + assert_eq!( + screen.tab_history, + vec![None, Some(0), Some(1), Some(2)], + "Tab history is invalid" + ); + assert_eq!( + screen.get_active_tab().unwrap().position, + 3, + "Active tab toggler to previous tab" + ); + + screen.switch_tab_prev(); + assert_eq!( + screen.tab_history, + vec![None, Some(0), Some(1), Some(3)], + "Tab history is invalid" + ); + assert_eq!( + screen.get_active_tab().unwrap().position, + 2, + "Active tab toggler to previous tab" + ); + screen.switch_tab_prev(); + assert_eq!( + screen.tab_history, + vec![None, Some(0), Some(3), Some(2)], + "Tab history is invalid" + ); + assert_eq!( + screen.get_active_tab().unwrap().position, + 1, + "Active tab toggler to previous tab" + ); + + screen.close_tab(); + assert_eq!( + screen.tab_history, + vec![None, Some(0), Some(3)], + "Tab history is invalid" + ); + assert_eq!( + screen.get_active_tab().unwrap().position, + 1, + "Active tab toggler to previous tab" + ); + + screen.toggle_tab(); + assert_eq!( + screen.get_active_tab().unwrap().position, + 2, + "Active tab toggler to previous tab" + ); + assert_eq!( + screen.tab_history, + vec![None, Some(0), Some(2)], + "Tab history is invalid" + ); +} diff --git a/zellij-server/src/unit/tab_tests.rs b/zellij-server/src/unit/tab_tests.rs index cc08a5b167..0859439766 100644 --- a/zellij-server/src/unit/tab_tests.rs +++ b/zellij-server/src/unit/tab_tests.rs @@ -96,6 +96,7 @@ fn create_new_tab(position_and_size: PositionAndSize) -> Tab { mode_info, colors, session_state, + true, // draw pane frames ) } @@ -136,7 +137,7 @@ fn split_panes_vertically() { .unwrap() .position_and_size() .cols, - 60, + 61, "first pane column count" ); assert_eq!( @@ -252,7 +253,7 @@ fn split_panes_horizontally() { .unwrap() .position_and_size() .y, - 11, + 10, "second pane y position" ); assert_eq!( @@ -270,7 +271,7 @@ fn split_panes_horizontally() { .unwrap() .position_and_size() .rows, - 9, + 10, "second pane row count" ); } @@ -315,7 +316,7 @@ fn split_largest_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "first pane column count" ); assert_eq!( @@ -380,7 +381,7 @@ fn split_largest_pane() { .unwrap() .position_and_size() .y, - 11, + 10, "third pane y position" ); assert_eq!( @@ -389,7 +390,7 @@ fn split_largest_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "third pane column count" ); assert_eq!( @@ -398,7 +399,7 @@ fn split_largest_pane() { .unwrap() .position_and_size() .rows, - 9, + 10, "third pane row count" ); @@ -417,7 +418,7 @@ fn split_largest_pane() { .unwrap() .position_and_size() .y, - 11, + 10, "fourth pane y position" ); assert_eq!( @@ -435,25 +436,11 @@ fn split_largest_pane() { .unwrap() .position_and_size() .rows, - 9, + 10, "fourth pane row count" ); } -#[test] -pub fn cannot_split_panes_vertically_when_active_terminal_is_too_small() { - let position_and_size = PositionAndSize { - cols: 8, - rows: 20, - x: 0, - y: 0, - ..Default::default() - }; - let mut tab = create_new_tab(position_and_size); - tab.vertical_split(PaneId::Terminal(2)); - assert_eq!(tab.panes.len(), 1, "Tab still has only one pane"); -} - #[test] pub fn cannot_split_panes_vertically_when_active_pane_is_too_small() { let position_and_size = PositionAndSize { @@ -539,7 +526,7 @@ pub fn toggle_focused_pane_fullscreen() { ); assert_eq!( tab.panes.get(&PaneId::Terminal(4)).unwrap().y(), - 11, + 10, "Pane y is on screen edge" ); assert_eq!( @@ -549,7 +536,7 @@ pub fn toggle_focused_pane_fullscreen() { ); assert_eq!( tab.panes.get(&PaneId::Terminal(4)).unwrap().rows(), - 9, + 10, "Pane rows match fullscreen rows" ); // we don't test if all other panes are hidden because this logic is done in the render @@ -889,7 +876,7 @@ pub fn close_pane_with_multiple_panes_above_it() { .unwrap() .position_and_size() .cols, - 60, + 61, "first remaining pane column count" ); assert_eq!( @@ -990,7 +977,7 @@ pub fn close_pane_with_multiple_panes_below_it() { .unwrap() .position_and_size() .cols, - 60, + 61, "first remaining pane column count" ); assert_eq!( @@ -1120,7 +1107,7 @@ pub fn close_pane_with_multiple_panes_to_the_left() { .unwrap() .position_and_size() .y, - 11, + 10, "second remaining pane y position" ); assert_eq!( @@ -1138,7 +1125,7 @@ pub fn close_pane_with_multiple_panes_to_the_left() { .unwrap() .position_and_size() .rows, - 9, + 10, "second remaining pane row count" ); } @@ -1221,7 +1208,7 @@ pub fn close_pane_with_multiple_panes_to_the_right() { .unwrap() .position_and_size() .y, - 11, + 10, "second remaining pane y position" ); assert_eq!( @@ -1239,7 +1226,7 @@ pub fn close_pane_with_multiple_panes_to_the_right() { .unwrap() .position_and_size() .rows, - 9, + 10, "second remaining pane row count" ); } @@ -1311,7 +1298,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 60, + 61, "first remaining pane column count" ); assert_eq!( @@ -1367,7 +1354,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .x, - 92, + 91, "third remaining pane x position" ); assert_eq!( @@ -1385,7 +1372,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 29, + 30, "third remaining pane column count" ); assert_eq!( @@ -1413,7 +1400,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 11, + 10, "fourth remaining pane y position" ); assert_eq!( @@ -1422,7 +1409,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 60, + 61, "fourth remaining pane column count" ); assert_eq!( @@ -1431,7 +1418,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 9, + 10, "fourth remaining pane row count" ); @@ -1441,7 +1428,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .x, - 92, + 91, "sixths remaining pane x position" ); assert_eq!( @@ -1450,7 +1437,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 11, + 10, "sixths remaining pane y position" ); assert_eq!( @@ -1459,7 +1446,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 29, + 30, "sixths remaining pane column count" ); assert_eq!( @@ -1468,7 +1455,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 9, + 10, "sixths remaining pane row count" ); @@ -1478,7 +1465,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .x, - 77, + 76, "seventh remaining pane x position" ); assert_eq!( @@ -1496,7 +1483,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 14, + 15, "seventh remaining pane column count" ); assert_eq!( @@ -1577,7 +1564,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 60, + 61, "first remaining pane column count" ); assert_eq!( @@ -1596,7 +1583,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .x, - 92, + 91, "third remaining pane x position" ); assert_eq!( @@ -1614,7 +1601,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 29, + 30, "third remaining pane column count" ); assert_eq!( @@ -1642,7 +1629,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 11, + 10, "fourth remaining pane y position" ); assert_eq!( @@ -1651,7 +1638,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 60, + 61, "fourth remaining pane column count" ); assert_eq!( @@ -1660,7 +1647,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 9, + 10, "fourth remaining pane row count" ); @@ -1707,7 +1694,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .x, - 92, + 91, "sixths remaining pane x position" ); assert_eq!( @@ -1716,7 +1703,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 11, + 10, "sixths remaining pane y position" ); assert_eq!( @@ -1725,7 +1712,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 29, + 30, "sixths remaining pane column count" ); assert_eq!( @@ -1734,7 +1721,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 9, + 10, "sixths remaining pane row count" ); @@ -1744,7 +1731,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .x, - 77, + 76, "seventh remaining pane x position" ); assert_eq!( @@ -1762,7 +1749,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 14, + 15, "seventh remaining pane column count" ); assert_eq!( @@ -1816,6 +1803,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { tab.move_focus_up(); tab.move_focus_left(); tab.resize_right(); + tab.resize_up(); tab.horizontal_split(new_pane_id_6); tab.move_focus_right(); tab.close_focused_pane(); @@ -1846,7 +1834,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 60, + 61, "first remaining pane column count" ); assert_eq!( @@ -1855,7 +1843,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 15, + 13, "first remaining pane row count" ); @@ -1874,7 +1862,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 16, + 13, "third remaining pane y position" ); assert_eq!( @@ -1892,7 +1880,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 3, + 5, "third remaining pane row count" ); @@ -1911,7 +1899,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 24, + 23, "fourth remaining pane y position" ); assert_eq!( @@ -1920,7 +1908,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 60, + 61, "fourth remaining pane column count" ); assert_eq!( @@ -1929,7 +1917,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 6, + 7, "fourth remaining pane row count" ); @@ -1966,7 +1954,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 15, + 13, "second remaining pane row count" ); @@ -1985,7 +1973,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 24, + 23, "sixths remaining pane y position" ); assert_eq!( @@ -2003,7 +1991,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 6, + 7, "sixths remaining pane row count" ); @@ -2022,7 +2010,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 20, + 18, "seventh remaining pane y position" ); assert_eq!( @@ -2040,7 +2028,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 3, + 5, "seventh remaining pane row count" ); } @@ -2084,6 +2072,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { tab.vertical_split(new_pane_id_5); tab.move_focus_up(); tab.resize_left(); + tab.resize_up(); tab.horizontal_split(new_pane_id_6); tab.move_focus_left(); tab.close_focused_pane(); @@ -2114,7 +2103,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 60, + 61, "first remaining pane column count" ); assert_eq!( @@ -2123,7 +2112,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 15, + 13, "first remaining pane row count" ); @@ -2142,7 +2131,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 24, + 23, "fourth remaining pane y position" ); assert_eq!( @@ -2151,7 +2140,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .cols, - 60, + 61, "fourth remaining pane column count" ); assert_eq!( @@ -2160,7 +2149,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 6, + 7, "fourth remaining pane row count" ); @@ -2197,7 +2186,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 15, + 13, "second remaining pane row count" ); @@ -2216,7 +2205,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 16, + 13, "third remaining pane y position" ); assert_eq!( @@ -2234,7 +2223,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 3, + 5, "third remaining pane row count" ); @@ -2253,7 +2242,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 24, + 23, "sixths remaining pane y position" ); assert_eq!( @@ -2271,7 +2260,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 6, + 7, "sixths remaining pane row count" ); @@ -2290,7 +2279,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .y, - 20, + 18, "seventh remaining pane y position" ); assert_eq!( @@ -2308,7 +2297,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { .unwrap() .position_and_size() .rows, - 3, + 5, "seventh remaining pane row count" ); } @@ -2331,7 +2320,7 @@ pub fn move_focus_down() { assert_eq!( tab.get_active_pane().unwrap().y(), - 11, + 10, "Active pane is the bottom one" ); } @@ -2358,12 +2347,12 @@ pub fn move_focus_down_to_the_most_recently_used_pane() { assert_eq!( tab.get_active_pane().unwrap().y(), - 11, + 10, "Active pane y position" ); assert_eq!( tab.get_active_pane().unwrap().x(), - 92, + 91, "Active pane x position" ); } @@ -2418,7 +2407,7 @@ pub fn move_focus_up_to_the_most_recently_used_pane() { ); assert_eq!( tab.get_active_pane().unwrap().x(), - 92, + 91, "Active pane x position" ); } @@ -2468,7 +2457,7 @@ pub fn move_focus_left_to_the_most_recently_used_pane() { assert_eq!( tab.get_active_pane().unwrap().y(), - 16, + 15, "Active pane y position" ); assert_eq!( @@ -2523,7 +2512,7 @@ pub fn move_focus_right_to_the_most_recently_used_pane() { assert_eq!( tab.get_active_pane().unwrap().y(), - 16, + 15, "Active pane y position" ); assert_eq!( @@ -2563,7 +2552,7 @@ pub fn resize_down_with_pane_above() { ); assert_eq!( tab.panes.get(&new_pane_id).unwrap().position_and_size().y, - 13, + 12, "focused pane y position" ); assert_eq!( @@ -2581,7 +2570,7 @@ pub fn resize_down_with_pane_above() { .unwrap() .position_and_size() .rows, - 7, + 8, "focused pane row count" ); @@ -2653,7 +2642,7 @@ pub fn resize_down_with_pane_below() { ); assert_eq!( tab.panes.get(&new_pane_id).unwrap().position_and_size().y, - 13, + 12, "pane below y position" ); assert_eq!( @@ -2671,7 +2660,7 @@ pub fn resize_down_with_pane_below() { .unwrap() .position_and_size() .rows, - 7, + 8, "pane below row count" ); @@ -2750,7 +2739,7 @@ pub fn resize_down_with_panes_above_and_below() { ); assert_eq!( tab.panes.get(&new_pane_id_1).unwrap().position_and_size().y, - 16, + 15, "focused pane y position" ); assert_eq!( @@ -2768,7 +2757,7 @@ pub fn resize_down_with_panes_above_and_below() { .unwrap() .position_and_size() .rows, - 9, + 10, "focused pane row count" ); @@ -2779,7 +2768,7 @@ pub fn resize_down_with_panes_above_and_below() { ); assert_eq!( tab.panes.get(&new_pane_id_2).unwrap().position_and_size().y, - 26, + 25, "pane below y position" ); assert_eq!( @@ -2797,7 +2786,7 @@ pub fn resize_down_with_panes_above_and_below() { .unwrap() .position_and_size() .rows, - 4, + 5, "pane below row count" ); @@ -2865,7 +2854,7 @@ pub fn resize_down_with_multiple_panes_above() { ); assert_eq!( tab.panes.get(&new_pane_id_1).unwrap().position_and_size().y, - 18, + 17, "focused pane y position" ); assert_eq!( @@ -2883,7 +2872,7 @@ pub fn resize_down_with_multiple_panes_above() { .unwrap() .position_and_size() .rows, - 12, + 13, "focused pane row count" ); @@ -2932,7 +2921,7 @@ pub fn resize_down_with_multiple_panes_above() { .unwrap() .position_and_size() .cols, - 60, + 61, "second pane above column count" ); assert_eq!( @@ -2982,7 +2971,7 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() { ); assert_eq!( tab.panes.get(&focused_pane).unwrap().position_and_size().y, - 18, + 17, "focused pane y position" ); assert_eq!( @@ -3000,7 +2989,7 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .rows, - 12, + 13, "focused pane row count" ); @@ -3028,7 +3017,7 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane above and to the left column count" ); assert_eq!( @@ -3077,7 +3066,7 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane to the left y position" ); assert_eq!( @@ -3086,7 +3075,7 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane to the left column count" ); assert_eq!( @@ -3095,7 +3084,7 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane to the left row count" ); } @@ -3182,7 +3171,7 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane above and to the left column count" ); assert_eq!( @@ -3202,7 +3191,7 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() { ); assert_eq!( tab.panes.get(&pane_below).unwrap().position_and_size().y, - 18, + 17, "pane above y position" ); assert_eq!( @@ -3212,7 +3201,7 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() { ); assert_eq!( tab.panes.get(&pane_below).unwrap().position_and_size().rows, - 12, + 13, "pane above row count" ); @@ -3231,7 +3220,7 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane to the left y position" ); assert_eq!( @@ -3240,7 +3229,7 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane to the left column count" ); assert_eq!( @@ -3249,7 +3238,7 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane to the left row count" ); } @@ -3292,7 +3281,7 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() { ); assert_eq!( tab.panes.get(&focused_pane).unwrap().position_and_size().y, - 18, + 17, "focused pane y position" ); assert_eq!( @@ -3301,7 +3290,7 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "focused pane column count" ); assert_eq!( @@ -3310,7 +3299,7 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 12, + 13, "focused pane row count" ); @@ -3326,7 +3315,7 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() { ); assert_eq!( tab.panes.get(&pane_above).unwrap().position_and_size().cols, - 60, + 61, "pane above column count" ); assert_eq!( @@ -3350,7 +3339,7 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane to the right y position" ); assert_eq!( @@ -3368,7 +3357,7 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane to the right row count" ); @@ -3456,7 +3445,7 @@ pub fn resize_down_with_panes_below_aligned_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "focused pane column count" ); assert_eq!( @@ -3476,17 +3465,17 @@ pub fn resize_down_with_panes_below_aligned_right_with_current_pane() { ); assert_eq!( tab.panes.get(&pane_below).unwrap().position_and_size().y, - 18, + 17, "pane below y position" ); assert_eq!( tab.panes.get(&pane_below).unwrap().position_and_size().cols, - 60, + 61, "pane below column count" ); assert_eq!( tab.panes.get(&pane_below).unwrap().position_and_size().rows, - 12, + 13, "pane below row count" ); @@ -3505,7 +3494,7 @@ pub fn resize_down_with_panes_below_aligned_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane below and to the right y position" ); assert_eq!( @@ -3523,7 +3512,7 @@ pub fn resize_down_with_panes_below_aligned_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane below and to the right row count" ); @@ -3618,7 +3607,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -3646,7 +3635,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -3655,7 +3644,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -3664,7 +3653,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -3683,7 +3672,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 18, + 17, "pane 3 y position" ); assert_eq!( @@ -3701,7 +3690,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 12, + 13, "pane 3 row count" ); @@ -3711,7 +3700,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .x, - 92, + 91, "pane 4 x position" ); assert_eq!( @@ -3720,7 +3709,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 4 y position" ); assert_eq!( @@ -3729,7 +3718,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 29, + 30, "pane 4 column count" ); assert_eq!( @@ -3738,7 +3727,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 4 row count" ); @@ -3785,7 +3774,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .x, - 92, + 91, "pane 6 x position" ); assert_eq!( @@ -3803,7 +3792,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 29, + 30, "pane 6 column count" ); assert_eq!( @@ -3869,7 +3858,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -3897,7 +3886,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -3906,7 +3895,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -3915,7 +3904,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -3934,7 +3923,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 18, + 17, "pane 3 y position" ); assert_eq!( @@ -3952,7 +3941,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 12, + 13, "pane 3 row count" ); @@ -3962,7 +3951,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .x, - 92, + 91, "pane 4 x position" ); assert_eq!( @@ -3971,7 +3960,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 4 y position" ); assert_eq!( @@ -3980,7 +3969,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 29, + 30, "pane 4 column count" ); assert_eq!( @@ -3989,7 +3978,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 4 row count" ); @@ -4036,7 +4025,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .x, - 92, + 91, "pane 6 x position" ); assert_eq!( @@ -4054,7 +4043,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 29, + 30, "pane 6 column count" ); assert_eq!( @@ -4124,7 +4113,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -4152,7 +4141,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -4161,7 +4150,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -4170,7 +4159,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -4217,7 +4206,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .x, - 92, + 91, "pane 4 x position" ); assert_eq!( @@ -4235,7 +4224,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .cols, - 29, + 30, "pane 4 column count" ); assert_eq!( @@ -4263,7 +4252,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .y, - 18, + 17, "pane 5 y position" ); assert_eq!( @@ -4281,7 +4270,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .rows, - 12, + 13, "pane 5 row count" ); @@ -4291,7 +4280,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .x, - 92, + 91, "pane 6 x position" ); assert_eq!( @@ -4300,7 +4289,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .y, - 16, + 15, "pane 6 y position" ); assert_eq!( @@ -4309,7 +4298,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .cols, - 29, + 30, "pane 6 column count" ); assert_eq!( @@ -4318,7 +4307,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .rows, - 14, + 15, "pane 6 row count" ); @@ -4328,7 +4317,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .x, - 77, + 76, "pane 7 x position" ); assert_eq!( @@ -4337,7 +4326,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .y, - 18, + 17, "pane 7 y position" ); assert_eq!( @@ -4346,7 +4335,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .cols, - 7, + 8, "pane 7 column count" ); assert_eq!( @@ -4355,7 +4344,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .rows, - 12, + 13, "pane 7 row count" ); @@ -4365,7 +4354,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .x, - 85, + 84, "pane 8 x position" ); assert_eq!( @@ -4374,7 +4363,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .y, - 18, + 17, "pane 8 y position" ); assert_eq!( @@ -4383,7 +4372,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .cols, - 6, + 7, "pane 8 column count" ); assert_eq!( @@ -4392,7 +4381,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef .unwrap() .position_and_size() .rows, - 12, + 13, "pane 8 row count" ); } @@ -4455,7 +4444,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -4483,7 +4472,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -4492,7 +4481,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -4501,7 +4490,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -4548,7 +4537,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .x, - 92, + 91, "pane 4 x position" ); assert_eq!( @@ -4566,7 +4555,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .cols, - 29, + 30, "pane 4 column count" ); assert_eq!( @@ -4585,7 +4574,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .x, - 77, + 76, "pane 5 x position" ); assert_eq!( @@ -4603,7 +4592,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .cols, - 7, + 8, "pane 5 column count" ); assert_eq!( @@ -4622,7 +4611,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .x, - 85, + 84, "pane 6 x position" ); assert_eq!( @@ -4640,7 +4629,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .cols, - 6, + 7, "pane 6 column count" ); assert_eq!( @@ -4668,7 +4657,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .y, - 18, + 17, "pane 7 y position" ); assert_eq!( @@ -4686,7 +4675,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .rows, - 12, + 13, "pane 7 row count" ); @@ -4696,7 +4685,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .x, - 92, + 91, "pane 8 x position" ); assert_eq!( @@ -4705,7 +4694,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .y, - 16, + 15, "pane 8 y position" ); assert_eq!( @@ -4714,7 +4703,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .cols, - 29, + 30, "pane 8 column count" ); assert_eq!( @@ -4723,7 +4712,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ .unwrap() .position_and_size() .rows, - 14, + 15, "pane 8 row count" ); } @@ -4739,7 +4728,7 @@ pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() { let position_and_size = PositionAndSize { cols: 121, - rows: 7, + rows: 10, x: 0, y: 0, ..Default::default() @@ -4755,7 +4744,7 @@ pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() { .unwrap() .position_and_size() .rows, - 3, + 5, "pane 1 height stayed the same" ); assert_eq!( @@ -4764,7 +4753,7 @@ pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() { .unwrap() .position_and_size() .rows, - 3, + 5, "pane 2 height stayed the same" ); } @@ -4813,7 +4802,7 @@ pub fn resize_left_with_pane_to_the_left() { .unwrap() .position_and_size() .cols, - 50, + 51, "pane 1 column count" ); assert_eq!( @@ -4908,7 +4897,7 @@ pub fn resize_left_with_pane_to_the_right() { .unwrap() .position_and_size() .cols, - 50, + 51, "pane 1 column count" ); assert_eq!( @@ -5005,7 +4994,7 @@ pub fn resize_left_with_panes_to_the_left_and_right() { .unwrap() .position_and_size() .cols, - 50, + 51, "pane 1 column count" ); assert_eq!( @@ -5061,7 +5050,7 @@ pub fn resize_left_with_panes_to_the_left_and_right() { .unwrap() .position_and_size() .x, - 92, + 91, "pane 2 x position" ); assert_eq!( @@ -5079,7 +5068,7 @@ pub fn resize_left_with_panes_to_the_left_and_right() { .unwrap() .position_and_size() .cols, - 29, + 30, "pane 2 column count" ); assert_eq!( @@ -5139,7 +5128,7 @@ pub fn resize_left_with_multiple_panes_to_the_left() { .unwrap() .position_and_size() .cols, - 50, + 51, "pane 1 column count" ); assert_eq!( @@ -5204,7 +5193,7 @@ pub fn resize_left_with_multiple_panes_to_the_left() { .unwrap() .position_and_size() .y, - 11, + 10, "pane 2 y position" ); assert_eq!( @@ -5213,7 +5202,7 @@ pub fn resize_left_with_multiple_panes_to_the_left() { .unwrap() .position_and_size() .cols, - 50, + 51, "pane 2 column count" ); assert_eq!( @@ -5222,7 +5211,7 @@ pub fn resize_left_with_multiple_panes_to_the_left() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 2 row count" ); } @@ -5275,7 +5264,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -5303,7 +5292,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -5312,7 +5301,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .cols, - 50, + 51, "pane 2 column count" ); assert_eq!( @@ -5321,7 +5310,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -5340,7 +5329,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 3 y position" ); assert_eq!( @@ -5358,7 +5347,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 3 row count" ); @@ -5449,7 +5438,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -5477,7 +5466,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -5486,7 +5475,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .cols, - 50, + 51, "pane 2 column count" ); assert_eq!( @@ -5495,7 +5484,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -5514,7 +5503,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 3 y position" ); assert_eq!( @@ -5532,7 +5521,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 3 row count" ); @@ -5621,7 +5610,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .cols, - 50, + 51, "pane 1 column count" ); assert_eq!( @@ -5649,7 +5638,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -5658,7 +5647,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -5667,7 +5656,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -5686,7 +5675,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 3 y position" ); assert_eq!( @@ -5704,7 +5693,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 3 row count" ); @@ -5794,7 +5783,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .cols, - 50, + 51, "pane 1 column count" ); assert_eq!( @@ -5822,7 +5811,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -5831,7 +5820,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -5840,7 +5829,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -5859,7 +5848,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 3 y position" ); assert_eq!( @@ -5877,7 +5866,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 3 row count" ); @@ -5972,7 +5961,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -6000,7 +5989,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -6009,7 +5998,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .cols, - 50, + 51, "pane 2 column count" ); assert_eq!( @@ -6018,7 +6007,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .rows, - 7, + 8, "pane 2 row count" ); @@ -6037,7 +6026,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .y, - 24, + 23, "pane 3 y position" ); assert_eq!( @@ -6046,7 +6035,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .cols, - 60, + 61, "pane 3 column count" ); assert_eq!( @@ -6055,7 +6044,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .rows, - 6, + 7, "pane 3 row count" ); @@ -6074,7 +6063,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .y, - 24, + 23, "pane 4 y position" ); assert_eq!( @@ -6092,7 +6081,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .rows, - 6, + 7, "pane 4 row count" ); @@ -6111,7 +6100,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .y, - 16, + 15, "pane 5 y position" ); assert_eq!( @@ -6129,7 +6118,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa .unwrap() .position_and_size() .rows, - 7, + 8, "pane 5 row count" ); @@ -6225,7 +6214,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -6253,7 +6242,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -6262,7 +6251,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .cols, - 50, + 51, "pane 2 column count" ); assert_eq!( @@ -6271,7 +6260,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .rows, - 7, + 8, "pane 2 row count" ); @@ -6290,7 +6279,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .y, - 24, + 23, "pane 3 y position" ); assert_eq!( @@ -6299,7 +6288,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .cols, - 60, + 61, "pane 3 column count" ); assert_eq!( @@ -6308,7 +6297,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .rows, - 6, + 7, "pane 3 row count" ); @@ -6327,7 +6316,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .y, - 24, + 23, "pane 4 y position" ); assert_eq!( @@ -6345,7 +6334,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .rows, - 6, + 7, "pane 4 row count" ); @@ -6364,7 +6353,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .y, - 16, + 15, "pane 5 y position" ); assert_eq!( @@ -6382,7 +6371,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .rows, - 7, + 8, "pane 5 row count" ); @@ -6450,6 +6439,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov tab.move_focus_up(); tab.vertical_split(PaneId::Terminal(5)); tab.move_focus_down(); + tab.resize_down(); tab.vertical_split(PaneId::Terminal(6)); tab.horizontal_split(PaneId::Terminal(7)); tab.horizontal_split(PaneId::Terminal(8)); @@ -6480,7 +6470,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -6508,7 +6498,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .y, - 36, + 35, "pane 2 y position" ); assert_eq!( @@ -6517,7 +6507,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .cols, - 50, + 51, "pane 2 column count" ); assert_eq!( @@ -6526,7 +6516,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .rows, - 17, + 20, "pane 2 row count" ); @@ -6545,7 +6535,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .y, - 54, + 55, "pane 3 y position" ); assert_eq!( @@ -6554,7 +6544,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .cols, - 60, + 61, "pane 3 column count" ); assert_eq!( @@ -6563,7 +6553,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .rows, - 16, + 15, "pane 3 row count" ); @@ -6582,7 +6572,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .y, - 54, + 55, "pane 4 y position" ); assert_eq!( @@ -6600,7 +6590,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .rows, - 16, + 15, "pane 4 row count" ); @@ -6656,7 +6646,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .y, - 36, + 35, "pane 6 y position" ); assert_eq!( @@ -6674,7 +6664,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .rows, - 8, + 10, "pane 6 row count" ); @@ -6711,7 +6701,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .rows, - 4, + 5, "pane 7 row count" ); @@ -6748,7 +6738,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov .unwrap() .position_and_size() .rows, - 3, + 5, "pane 8 row count" ); } @@ -6779,6 +6769,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo tab.move_focus_up(); tab.vertical_split(PaneId::Terminal(5)); tab.move_focus_down(); + tab.resize_down(); tab.vertical_split(PaneId::Terminal(6)); tab.move_focus_left(); tab.horizontal_split(PaneId::Terminal(7)); @@ -6810,7 +6801,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -6838,7 +6829,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .y, - 36, + 35, "pane 2 y position" ); assert_eq!( @@ -6847,7 +6838,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .cols, - 50, + 51, "pane 2 column count" ); assert_eq!( @@ -6856,7 +6847,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 8, + 10, "pane 2 row count" ); @@ -6875,7 +6866,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .y, - 54, + 55, "pane 3 y position" ); assert_eq!( @@ -6884,7 +6875,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .cols, - 60, + 61, "pane 3 column count" ); assert_eq!( @@ -6893,7 +6884,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 16, + 15, "pane 3 row count" ); @@ -6912,7 +6903,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .y, - 54, + 55, "pane 4 y position" ); assert_eq!( @@ -6930,7 +6921,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 16, + 15, "pane 4 row count" ); @@ -6986,7 +6977,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .y, - 36, + 35, "pane 6 y position" ); assert_eq!( @@ -7004,7 +6995,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 17, + 20, "pane 6 row count" ); @@ -7032,7 +7023,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .cols, - 50, + 51, "pane 7 column count" ); assert_eq!( @@ -7041,7 +7032,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 4, + 5, "pane 7 row count" ); @@ -7069,7 +7060,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .cols, - 50, + 51, "pane 8 column count" ); assert_eq!( @@ -7078,7 +7069,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 3, + 5, "pane 8 row count" ); } @@ -7093,7 +7084,7 @@ pub fn cannot_resize_left_when_pane_to_the_left_is_at_minimum_width() { // █ == focused pane let position_and_size = PositionAndSize { - cols: 9, + cols: 10, rows: 20, x: 0, y: 0, @@ -7109,7 +7100,7 @@ pub fn cannot_resize_left_when_pane_to_the_left_is_at_minimum_width() { .unwrap() .position_and_size() .cols, - 4, + 5, "pane 1 columns stayed the same" ); assert_eq!( @@ -7118,7 +7109,7 @@ pub fn cannot_resize_left_when_pane_to_the_left_is_at_minimum_width() { .unwrap() .position_and_size() .cols, - 4, + 5, "pane 2 columns stayed the same" ); } @@ -7167,7 +7158,7 @@ pub fn resize_right_with_pane_to_the_left() { .unwrap() .position_and_size() .cols, - 70, + 71, "pane 1 column count" ); assert_eq!( @@ -7263,7 +7254,7 @@ pub fn resize_right_with_pane_to_the_right() { .unwrap() .position_and_size() .cols, - 70, + 71, "pane 1 column count" ); assert_eq!( @@ -7360,7 +7351,7 @@ pub fn resize_right_with_panes_to_the_left_and_right() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -7416,7 +7407,7 @@ pub fn resize_right_with_panes_to_the_left_and_right() { .unwrap() .position_and_size() .x, - 102, + 101, "pane 2 x position" ); assert_eq!( @@ -7434,7 +7425,7 @@ pub fn resize_right_with_panes_to_the_left_and_right() { .unwrap() .position_and_size() .cols, - 19, + 20, "pane 2 column count" ); assert_eq!( @@ -7495,7 +7486,7 @@ pub fn resize_right_with_multiple_panes_to_the_left() { .unwrap() .position_and_size() .cols, - 70, + 71, "pane 1 column count" ); assert_eq!( @@ -7560,7 +7551,7 @@ pub fn resize_right_with_multiple_panes_to_the_left() { .unwrap() .position_and_size() .y, - 11, + 10, "pane 3 y position" ); assert_eq!( @@ -7569,7 +7560,7 @@ pub fn resize_right_with_multiple_panes_to_the_left() { .unwrap() .position_and_size() .cols, - 70, + 71, "pane 3 column count" ); assert_eq!( @@ -7578,7 +7569,7 @@ pub fn resize_right_with_multiple_panes_to_the_left() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 3 row count" ); } @@ -7631,7 +7622,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -7696,7 +7687,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .y, - 11, + 10, "pane 3 y position" ); assert_eq!( @@ -7705,7 +7696,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .cols, - 70, + 71, "pane 3 column count" ); assert_eq!( @@ -7714,7 +7705,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 3 row count" ); @@ -7733,7 +7724,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .y, - 11, + 10, "pane 4 y position" ); assert_eq!( @@ -7751,7 +7742,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 4 row count" ); } @@ -7804,7 +7795,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -7869,7 +7860,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .y, - 11, + 10, "pane 3 y position" ); assert_eq!( @@ -7878,7 +7869,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .cols, - 70, + 71, "pane 3 column count" ); assert_eq!( @@ -7887,7 +7878,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 3 row count" ); @@ -7906,7 +7897,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .y, - 11, + 10, "pane 4 y position" ); assert_eq!( @@ -7924,7 +7915,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 4 row count" ); } @@ -7978,7 +7969,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .cols, - 70, + 71, "pane 1 column count" ); assert_eq!( @@ -8043,7 +8034,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .y, - 11, + 10, "pane 3 y position" ); assert_eq!( @@ -8052,7 +8043,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 3 column count" ); assert_eq!( @@ -8061,7 +8052,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 3 row count" ); @@ -8080,7 +8071,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .y, - 11, + 10, "pane 4 y position" ); assert_eq!( @@ -8098,7 +8089,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 4 row count" ); } @@ -8153,7 +8144,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .cols, - 70, + 71, "pane 1 column count" ); assert_eq!( @@ -8218,7 +8209,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .y, - 11, + 10, "pane 3 y position" ); assert_eq!( @@ -8227,7 +8218,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 3 column count" ); assert_eq!( @@ -8236,7 +8227,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 3 row count" ); @@ -8255,7 +8246,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .y, - 11, + 10, "pane 4 y position" ); assert_eq!( @@ -8273,7 +8264,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 4 row count" ); } @@ -8331,7 +8322,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -8359,7 +8350,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .y, - 11, + 10, "pane 2 y position" ); assert_eq!( @@ -8368,7 +8359,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .cols, - 70, + 71, "pane 2 column count" ); assert_eq!( @@ -8377,7 +8368,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .rows, - 4, + 5, "pane 2 row count" ); @@ -8396,7 +8387,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .y, - 16, + 15, "pane 3 y position" ); assert_eq!( @@ -8405,7 +8396,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .cols, - 60, + 61, "pane 3 column count" ); assert_eq!( @@ -8414,7 +8405,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .rows, - 4, + 5, "pane 3 row count" ); @@ -8433,7 +8424,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .y, - 16, + 15, "pane 4 y position" ); assert_eq!( @@ -8451,7 +8442,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .rows, - 4, + 5, "pane 4 row count" ); @@ -8470,7 +8461,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .y, - 11, + 10, "pane 5 y position" ); assert_eq!( @@ -8488,7 +8479,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p .unwrap() .position_and_size() .rows, - 4, + 5, "pane 5 row count" ); @@ -8583,7 +8574,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -8611,7 +8602,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .y, - 11, + 10, "pane 2 y position" ); assert_eq!( @@ -8620,7 +8611,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .cols, - 70, + 71, "pane 2 column count" ); assert_eq!( @@ -8629,7 +8620,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .rows, - 4, + 5, "pane 2 row count" ); @@ -8648,7 +8639,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .y, - 16, + 15, "pane 3 y position" ); assert_eq!( @@ -8657,7 +8648,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .cols, - 60, + 61, "pane 3 column count" ); assert_eq!( @@ -8666,7 +8657,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .rows, - 4, + 5, "pane 3 row count" ); @@ -8685,7 +8676,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .y, - 16, + 15, "pane 4 y position" ); assert_eq!( @@ -8703,7 +8694,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .rows, - 4, + 5, "pane 4 row count" ); @@ -8722,7 +8713,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .y, - 11, + 10, "pane 5 y position" ); assert_eq!( @@ -8740,7 +8731,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ .unwrap() .position_and_size() .rows, - 4, + 5, "pane 5 row count" ); @@ -8807,6 +8798,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo tab.move_focus_up(); tab.vertical_split(PaneId::Terminal(5)); tab.move_focus_down(); + tab.resize_up(); tab.vertical_split(PaneId::Terminal(6)); tab.horizontal_split(PaneId::Terminal(7)); tab.horizontal_split(PaneId::Terminal(8)); @@ -8837,7 +8829,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -8846,7 +8838,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 35, + 33, "pane 1 row count" ); @@ -8865,7 +8857,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .y, - 36, + 33, "pane 2 y position" ); assert_eq!( @@ -8874,7 +8866,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .cols, - 70, + 71, "pane 2 column count" ); assert_eq!( @@ -8883,7 +8875,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 17, + 20, "pane 2 row count" ); @@ -8902,7 +8894,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .y, - 54, + 53, "pane 3 y position" ); assert_eq!( @@ -8911,7 +8903,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .cols, - 60, + 61, "pane 3 column count" ); assert_eq!( @@ -8920,7 +8912,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 16, + 17, "pane 3 row count" ); @@ -8939,7 +8931,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .y, - 54, + 53, "pane 4 y position" ); assert_eq!( @@ -8957,7 +8949,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 16, + 17, "pane 4 row count" ); @@ -8994,7 +8986,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 35, + 33, "pane 5 row count" ); @@ -9013,7 +9005,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .y, - 36, + 33, "pane 6 y position" ); assert_eq!( @@ -9031,7 +9023,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 8, + 10, "pane 6 row count" ); @@ -9050,7 +9042,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .y, - 45, + 43, "pane 7 y position" ); assert_eq!( @@ -9068,7 +9060,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 4, + 5, "pane 7 row count" ); @@ -9087,7 +9079,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .y, - 50, + 48, "pane 8 y position" ); assert_eq!( @@ -9105,7 +9097,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo .unwrap() .position_and_size() .rows, - 3, + 5, "pane 8 row count" ); } @@ -9135,6 +9127,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab tab.move_focus_up(); tab.vertical_split(PaneId::Terminal(5)); tab.move_focus_down(); + tab.resize_up(); tab.vertical_split(PaneId::Terminal(6)); tab.move_focus_left(); tab.horizontal_split(PaneId::Terminal(7)); @@ -9166,7 +9159,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -9175,7 +9168,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .rows, - 35, + 33, "pane 1 row count" ); @@ -9194,7 +9187,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .y, - 36, + 33, "pane 2 y position" ); assert_eq!( @@ -9203,7 +9196,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .cols, - 70, + 71, "pane 2 column count" ); assert_eq!( @@ -9212,7 +9205,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .rows, - 8, + 10, "pane 2 row count" ); @@ -9231,7 +9224,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .y, - 54, + 53, "pane 3 y position" ); assert_eq!( @@ -9240,7 +9233,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .cols, - 60, + 61, "pane 3 column count" ); assert_eq!( @@ -9249,7 +9242,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .rows, - 16, + 17, "pane 3 row count" ); @@ -9268,7 +9261,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .y, - 54, + 53, "pane 4 y position" ); assert_eq!( @@ -9286,7 +9279,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .rows, - 16, + 17, "pane 4 row count" ); @@ -9323,7 +9316,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .rows, - 35, + 33, "pane 5 row count" ); @@ -9342,7 +9335,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .y, - 36, + 33, "pane 6 y position" ); assert_eq!( @@ -9360,7 +9353,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .rows, - 17, + 20, "pane 6 row count" ); @@ -9379,7 +9372,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .y, - 45, + 43, "pane 7 y position" ); assert_eq!( @@ -9388,7 +9381,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .cols, - 70, + 71, "pane 7 column count" ); assert_eq!( @@ -9397,7 +9390,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .rows, - 4, + 5, "pane 7 row count" ); @@ -9416,7 +9409,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .y, - 50, + 48, "pane 8 y position" ); assert_eq!( @@ -9425,7 +9418,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .cols, - 70, + 71, "pane 8 column count" ); assert_eq!( @@ -9434,7 +9427,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab .unwrap() .position_and_size() .rows, - 3, + 5, "pane 8 row count" ); } @@ -9448,7 +9441,7 @@ pub fn cannot_resize_right_when_pane_to_the_left_is_at_minimum_width() { // └─┴─┘ └─┴─┘ // █ == focused pane let position_and_size = PositionAndSize { - cols: 9, + cols: 10, rows: 20, x: 0, y: 0, @@ -9464,7 +9457,7 @@ pub fn cannot_resize_right_when_pane_to_the_left_is_at_minimum_width() { .unwrap() .position_and_size() .cols, - 4, + 5, "pane 1 columns stayed the same" ); assert_eq!( @@ -9473,7 +9466,7 @@ pub fn cannot_resize_right_when_pane_to_the_left_is_at_minimum_width() { .unwrap() .position_and_size() .cols, - 4, + 5, "pane 2 columns stayed the same" ); } @@ -9551,7 +9544,7 @@ pub fn resize_up_with_pane_above() { .unwrap() .position_and_size() .y, - 9, + 8, "pane 2 y position" ); assert_eq!( @@ -9569,7 +9562,7 @@ pub fn resize_up_with_pane_above() { .unwrap() .position_and_size() .rows, - 11, + 12, "pane 2 row count" ); } @@ -9648,7 +9641,7 @@ pub fn resize_up_with_pane_below() { .unwrap() .position_and_size() .y, - 9, + 8, "pane 2 y position" ); assert_eq!( @@ -9666,7 +9659,7 @@ pub fn resize_up_with_pane_below() { .unwrap() .position_and_size() .rows, - 11, + 12, "pane 2 row count" ); } @@ -9749,7 +9742,7 @@ pub fn resize_up_with_panes_above_and_below() { .unwrap() .position_and_size() .y, - 14, + 13, "pane 2 y position" ); assert_eq!( @@ -9767,7 +9760,7 @@ pub fn resize_up_with_panes_above_and_below() { .unwrap() .position_and_size() .rows, - 9, + 10, "pane 2 row count" ); @@ -9786,7 +9779,7 @@ pub fn resize_up_with_panes_above_and_below() { .unwrap() .position_and_size() .y, - 24, + 23, "pane 3 y position" ); assert_eq!( @@ -9804,7 +9797,7 @@ pub fn resize_up_with_panes_above_and_below() { .unwrap() .position_and_size() .rows, - 6, + 7, "pane 3 row count" ); } @@ -9856,7 +9849,7 @@ pub fn resize_up_with_multiple_panes_above() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -9884,7 +9877,7 @@ pub fn resize_up_with_multiple_panes_above() { .unwrap() .position_and_size() .y, - 14, + 13, "pane 2 y position" ); assert_eq!( @@ -9902,7 +9895,7 @@ pub fn resize_up_with_multiple_panes_above() { .unwrap() .position_and_size() .rows, - 16, + 17, "pane 2 row count" ); @@ -9991,7 +9984,7 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -10019,7 +10012,7 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -10028,7 +10021,7 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -10037,7 +10030,7 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -10093,7 +10086,7 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .y, - 14, + 13, "pane 4 y position" ); assert_eq!( @@ -10111,7 +10104,7 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() { .unwrap() .position_and_size() .rows, - 16, + 17, "pane 4 row count" ); } @@ -10166,7 +10159,7 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -10194,7 +10187,7 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -10203,7 +10196,7 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -10212,7 +10205,7 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -10268,7 +10261,7 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() { .unwrap() .position_and_size() .y, - 14, + 13, "pane 4 y position" ); assert_eq!( @@ -10286,7 +10279,7 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() { .unwrap() .position_and_size() .rows, - 16, + 17, "pane 4 row count" ); } @@ -10341,7 +10334,7 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -10369,7 +10362,7 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() { .unwrap() .position_and_size() .y, - 14, + 13, "pane 2 y position" ); assert_eq!( @@ -10378,7 +10371,7 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -10387,7 +10380,7 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 16, + 17, "pane 2 row count" ); @@ -10443,7 +10436,7 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 4 y position" ); assert_eq!( @@ -10461,7 +10454,7 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 4 row count" ); } @@ -10517,7 +10510,7 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -10545,7 +10538,7 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() { .unwrap() .position_and_size() .y, - 14, + 13, "pane 2 y position" ); assert_eq!( @@ -10554,7 +10547,7 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -10563,7 +10556,7 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 16, + 17, "pane 2 row count" ); @@ -10619,7 +10612,7 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 4 y position" ); assert_eq!( @@ -10637,7 +10630,7 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 4 row count" ); } @@ -10693,7 +10686,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -10721,7 +10714,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -10730,7 +10723,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -10739,7 +10732,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -10758,7 +10751,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 14, + 13, "pane 3 y position" ); assert_eq!( @@ -10776,7 +10769,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 16, + 17, "pane 3 row count" ); @@ -10786,7 +10779,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .x, - 92, + 91, "pane 4 x position" ); assert_eq!( @@ -10795,7 +10788,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 4 y position" ); assert_eq!( @@ -10804,7 +10797,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 29, + 30, "pane 4 column count" ); assert_eq!( @@ -10813,7 +10806,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 4 row count" ); @@ -10860,7 +10853,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .x, - 92, + 91, "pane 6 x position" ); assert_eq!( @@ -10878,7 +10871,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 29, + 30, "pane 6 column count" ); assert_eq!( @@ -10944,7 +10937,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -10972,7 +10965,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -10981,7 +10974,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -10990,7 +10983,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -11009,7 +11002,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 14, + 13, "pane 3 y position" ); assert_eq!( @@ -11027,7 +11020,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 16, + 17, "pane 3 row count" ); @@ -11037,7 +11030,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .x, - 92, + 91, "pane 4 x position" ); assert_eq!( @@ -11046,7 +11039,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .y, - 16, + 15, "pane 4 y position" ); assert_eq!( @@ -11055,7 +11048,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 29, + 30, "pane 4 column count" ); assert_eq!( @@ -11064,7 +11057,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .rows, - 14, + 15, "pane 4 row count" ); @@ -11111,7 +11104,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .x, - 92, + 91, "pane 6 x position" ); assert_eq!( @@ -11129,7 +11122,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { .unwrap() .position_and_size() .cols, - 29, + 30, "pane 6 column count" ); assert_eq!( @@ -11198,7 +11191,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -11226,7 +11219,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -11235,7 +11228,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -11244,7 +11237,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -11291,7 +11284,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .x, - 92, + 91, "pane 4 x position" ); assert_eq!( @@ -11309,7 +11302,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .cols, - 29, + 30, "pane 4 column count" ); assert_eq!( @@ -11337,7 +11330,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .y, - 14, + 13, "pane 5 y position" ); assert_eq!( @@ -11355,7 +11348,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .rows, - 16, + 17, "pane 5 row count" ); @@ -11365,7 +11358,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .x, - 92, + 91, "pane 6 x position" ); assert_eq!( @@ -11374,7 +11367,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .y, - 16, + 15, "pane 6 y position" ); assert_eq!( @@ -11383,7 +11376,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .cols, - 29, + 30, "pane 6 column count" ); assert_eq!( @@ -11392,7 +11385,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .rows, - 14, + 15, "pane 6 row count" ); @@ -11402,7 +11395,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .x, - 77, + 76, "pane 7 x position" ); assert_eq!( @@ -11411,7 +11404,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .y, - 14, + 13, "pane 7 y position" ); assert_eq!( @@ -11420,7 +11413,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .cols, - 7, + 8, "pane 7 column count" ); assert_eq!( @@ -11429,7 +11422,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .rows, - 16, + 17, "pane 7 row count" ); @@ -11439,7 +11432,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .x, - 85, + 84, "pane 8 x position" ); assert_eq!( @@ -11448,7 +11441,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .y, - 14, + 13, "pane 8 y position" ); assert_eq!( @@ -11457,7 +11450,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .cols, - 6, + 7, "pane 8 column count" ); assert_eq!( @@ -11466,7 +11459,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ .unwrap() .position_and_size() .rows, - 16, + 17, "pane 8 row count" ); } @@ -11527,7 +11520,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .cols, - 60, + 61, "pane 1 column count" ); assert_eq!( @@ -11555,7 +11548,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .y, - 16, + 15, "pane 2 y position" ); assert_eq!( @@ -11564,7 +11557,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .cols, - 60, + 61, "pane 2 column count" ); assert_eq!( @@ -11573,7 +11566,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .rows, - 14, + 15, "pane 2 row count" ); @@ -11620,7 +11613,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .x, - 92, + 91, "pane 4 x position" ); assert_eq!( @@ -11638,7 +11631,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .cols, - 29, + 30, "pane 4 column count" ); assert_eq!( @@ -11666,7 +11659,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .y, - 14, + 13, "pane 5 y position" ); assert_eq!( @@ -11684,7 +11677,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .rows, - 16, + 17, "pane 5 row count" ); @@ -11694,7 +11687,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .x, - 92, + 91, "pane 6 x position" ); assert_eq!( @@ -11703,7 +11696,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .y, - 16, + 15, "pane 6 y position" ); assert_eq!( @@ -11712,7 +11705,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .cols, - 29, + 30, "pane 6 column count" ); assert_eq!( @@ -11721,7 +11714,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .rows, - 14, + 15, "pane 6 row count" ); @@ -11731,7 +11724,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .x, - 77, + 76, "pane 7 x position" ); assert_eq!( @@ -11749,7 +11742,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .cols, - 7, + 8, "pane 7 column count" ); assert_eq!( @@ -11768,7 +11761,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .x, - 85, + 84, "pane 8 x position" ); assert_eq!( @@ -11786,7 +11779,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri .unwrap() .position_and_size() .cols, - 6, + 7, "pane 8 column count" ); assert_eq!( @@ -11811,7 +11804,7 @@ pub fn cannot_resize_up_when_pane_above_is_at_minimum_height() { let position_and_size = PositionAndSize { cols: 121, - rows: 7, + rows: 10, x: 0, y: 0, ..Default::default() @@ -11826,7 +11819,7 @@ pub fn cannot_resize_up_when_pane_above_is_at_minimum_height() { .unwrap() .position_and_size() .rows, - 3, + 5, "pane 1 height stayed the same" ); assert_eq!( @@ -11835,7 +11828,7 @@ pub fn cannot_resize_up_when_pane_above_is_at_minimum_height() { .unwrap() .position_and_size() .rows, - 3, + 5, "pane 2 height stayed the same" ); } diff --git a/zellij-server/src/wasm_vm.rs b/zellij-server/src/wasm_vm.rs index 41a468ee22..0f6b721e2a 100644 --- a/zellij-server/src/wasm_vm.rs +++ b/zellij-server/src/wasm_vm.rs @@ -133,15 +133,19 @@ pub(crate) fn wasm_thread_main(bus: Bus, 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, diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index cdf57e69ff..b22be626da 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -33,6 +33,8 @@ pub enum Event { TabUpdate(Vec), KeyPress(Key), Timer(f64), + CopyToClipboard, + InputReceived, } /// Describes the different input modes, which change the way that keystrokes will be interpreted. diff --git a/zellij-utils/assets/config/default.yaml b/zellij-utils/assets/config/default.yaml index 24ee95f597..42f2287b70 100644 --- a/zellij-utils/assets/config/default.yaml +++ b/zellij-utils/assets/config/default.yaml @@ -105,6 +105,8 @@ keybinds: key: [Char: 'x',] - action: [ToggleFocusFullscreen,] key: [Char: 'f',] + - action: [TogglePaneFrames,] + key: [Char: 'z',] - action: [FocusPreviousPane,] key: [ Alt: '[',] - action: [FocusNextPane,] @@ -134,7 +136,7 @@ keybinds: key: [ Char: 'h', Left, Up, Char: 'k',] - action: [GoToNextTab,] key: [ Char: 'l', Right,Down, Char: 'j'] - - action: [NewTab,] + - action: [NewTab: ,] key: [ Char: 'n',] - action: [CloseTab,] key: [ Char: 'x',] @@ -166,6 +168,8 @@ keybinds: key: [ Char: '8',] - action: [GoToTab: 9,] key: [ Char: '9',] + - action: [ToggleTab] + key: [ Char: "\t" ] scroll: - action: [SwitchToMode: Normal,] key: [Ctrl: 'r', Ctrl: 's', Char: ' ', diff --git a/zellij-utils/assets/layouts/default.yaml b/zellij-utils/assets/layouts/default.yaml index 96bf1809c9..e0a28da125 100644 --- a/zellij-utils/assets/layouts/default.yaml +++ b/zellij-utils/assets/layouts/default.yaml @@ -1,14 +1,20 @@ --- -direction: Horizontal -parts: +template: + direction: Horizontal + parts: + - direction: Vertical + borderless: true + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Vertical + body: true + - direction: Vertical + borderless: true + split_size: + Fixed: 2 + run: + plugin: status-bar +tabs: - direction: Vertical - split_size: - Fixed: 1 - run: - plugin: tab-bar - - direction: Vertical - - direction: Vertical - split_size: - Fixed: 2 - run: - plugin: status-bar diff --git a/zellij-utils/assets/layouts/disable-status-bar.yaml b/zellij-utils/assets/layouts/disable-status-bar.yaml index b990ba5002..a58ef4cf48 100644 --- a/zellij-utils/assets/layouts/disable-status-bar.yaml +++ b/zellij-utils/assets/layouts/disable-status-bar.yaml @@ -1,9 +1,12 @@ --- -direction: Horizontal -parts: - - direction: Vertical - split_size: - Fixed: 1 - run: - plugin: tab-bar - - direction: Vertical +template: + direction: Horizontal + parts: + - direction: Vertical + borderless: true + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Vertical + body: true diff --git a/zellij-utils/assets/layouts/strider.yaml b/zellij-utils/assets/layouts/strider.yaml index 9bbe5772fb..96e3c290e9 100644 --- a/zellij-utils/assets/layouts/strider.yaml +++ b/zellij-utils/assets/layouts/strider.yaml @@ -1,11 +1,22 @@ --- -direction: Horizontal -parts: - - direction: Vertical - split_size: - Fixed: 1 - run: - plugin: tab-bar +template: + direction: Horizontal + parts: + - direction: Vertical + borderless: true + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Vertical + body: true + - direction: Vertical + borderless: true + split_size: + Fixed: 2 + run: + plugin: status-bar +tabs: - direction: Vertical parts: - direction: Horizontal @@ -14,8 +25,3 @@ parts: run: plugin: strider - direction: Horizontal - - direction: Vertical - split_size: - Fixed: 2 - run: - plugin: status-bar diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 6a053038a3..65a54c5600 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -209,6 +209,7 @@ pub enum ScreenContext { CloseFocusedPane, ToggleActiveSyncTab, ToggleActiveTerminalFullscreen, + TogglePaneFrames, SetSelectable, SetInvisibleBorders, SetFixedHeight, @@ -227,6 +228,7 @@ pub enum ScreenContext { MouseRelease, MouseHold, Copy, + ToggleTab, } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 431a09bf7f..cd4ca4641f 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -1,6 +1,7 @@ //! Definition of the actions that can be bound to keys. use super::command::RunCommandAction; +use super::layout::TabLayout; use crate::input::options::OnForceClose; use serde::{Deserialize, Serialize}; use zellij_tile::data::InputMode; @@ -19,7 +20,7 @@ pub enum Direction { // As these actions are bound to the default config, please // do take care when refactoring - or renaming. // They might need to be adjusted in the default config -// as well `../../../assets/config/default.yaml` +// as well `../../assets/config/default.yaml` /// Actions that can be bound to keys. #[derive(Eq, Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum Action { @@ -56,6 +57,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). @@ -63,8 +66,8 @@ pub enum Action { NewPane(Option), /// Close the focus pane. CloseFocus, - /// Create a new tab. - NewTab, + /// Create a new tab, optionally with a specified tab layout. + NewTab(Option), /// Do nothing. NoOp, /// Go to the next tab. @@ -74,6 +77,7 @@ pub enum Action { /// Close the current tab. CloseTab, GoToTab(u32), + ToggleTab, TabNameInput(Vec), /// Run speficied command in new pane. Run(RunCommandAction), diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index 6a65109a74..930fe07afd 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -45,6 +45,9 @@ pub enum ConfigError { IoPath(io::Error, PathBuf), // Internal Deserialization Error FromUtf8(std::string::FromUtf8Error), + // Missing the tab section in the layout. + Layout(LayoutMissingTabSectionError), + LayoutPartAndTab(LayoutPartAndTabError), } impl Default for Config { @@ -129,6 +132,75 @@ impl Config { } } +// TODO: Split errors up into separate modules +#[derive(Debug, Clone)] +pub struct LayoutMissingTabSectionError; +#[derive(Debug, Clone)] +pub struct LayoutPartAndTabError; + +impl fmt::Display for LayoutMissingTabSectionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "MissingTabSectionError: +There needs to be exactly one `tabs` section specified in the layout file, for example: +--- +direction: Horizontal +parts: + - direction: Vertical + - direction: Vertical + tabs: + - direction: Vertical + - direction: Vertical + - direction: Vertical +" + ) + } +} + +impl std::error::Error for LayoutMissingTabSectionError { + fn description(&self) -> &str { + "One tab must be specified per Layout." + } +} + +impl fmt::Display for LayoutPartAndTabError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "LayoutPartAndTabError: +The `tabs` and `parts` section should not be specified on the same level in the layout file, for example: +--- +direction: Horizontal +parts: + - direction: Vertical + - direction: Vertical +tabs: + - direction: Vertical + - direction: Vertical + - direction: Vertical + +should rather be specified as: +--- +direction: Horizontal +parts: + - direction: Vertical + - direction: Vertical + tabs: + - direction: Vertical + - direction: Vertical + - direction: Vertical +" + ) + } +} + +impl std::error::Error for LayoutPartAndTabError { + fn description(&self) -> &str { + "The `tabs` and parts section should not be specified on the same level." + } +} + impl Display for ConfigError { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { match self { @@ -138,6 +210,12 @@ impl Display for ConfigError { } ConfigError::Serde(ref err) => write!(formatter, "Deserialization error: {}", err), ConfigError::FromUtf8(ref err) => write!(formatter, "FromUtf8Error: {}", err), + ConfigError::Layout(ref err) => { + write!(formatter, "There was an error in the layout file, {}", err) + } + ConfigError::LayoutPartAndTab(ref err) => { + write!(formatter, "There was an error in the layout file, {}", err) + } } } } @@ -149,6 +227,8 @@ impl std::error::Error for ConfigError { ConfigError::IoPath(ref err, _) => Some(err), ConfigError::Serde(ref err) => Some(err), ConfigError::FromUtf8(ref err) => Some(err), + ConfigError::Layout(ref err) => Some(err), + ConfigError::LayoutPartAndTab(ref err) => Some(err), } } } @@ -171,6 +251,18 @@ impl From for ConfigError { } } +impl From for ConfigError { + fn from(err: LayoutMissingTabSectionError) -> ConfigError { + ConfigError::Layout(err) + } +} + +impl From for ConfigError { + fn from(err: LayoutPartAndTabError) -> ConfigError { + ConfigError::LayoutPartAndTab(err) + } +} + // The unit test location. #[cfg(test)] mod config_test { diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 090ccb13fc..a3da6abab2 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -17,23 +17,24 @@ use crate::{serde, serde_yaml}; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; +use std::vec::Vec; use std::{fs::File, io::prelude::*}; -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(crate = "self::serde")] pub enum Direction { Horizontal, Vertical, } -#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[serde(crate = "self::serde")] pub enum SplitSize { Percent(u8), // 1 to 100 Fixed(u16), // An absolute number of columns or rows } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(crate = "self::serde")] pub enum Run { #[serde(rename = "plugin")] @@ -42,7 +43,8 @@ pub enum Run { Command(RunCommand), } -#[derive(Debug, Serialize, Deserialize, Clone)] +// The layout struct ultimately used to build the layouts. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(crate = "self::serde")] pub struct Layout { pub direction: Direction, @@ -50,25 +52,42 @@ pub struct Layout { pub parts: Vec, pub split_size: Option, pub run: Option, + #[serde(default)] + pub borderless: bool, +} + +// The struct that is used to deserialize the layout from +// a yaml configuration file +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(crate = "self::serde")] +#[serde(default)] +pub struct LayoutFromYaml { + #[serde(default)] + pub template: LayoutTemplate, + #[serde(default)] + pub borderless: bool, + #[serde(default)] + pub tabs: Vec, } -type LayoutResult = Result; +type LayoutFromYamlResult = Result; -impl Layout { - pub fn new(layout_path: &Path) -> LayoutResult { +impl LayoutFromYaml { + pub fn new(layout_path: &Path) -> LayoutFromYamlResult { let mut layout_file = File::open(&layout_path) .or_else(|_| File::open(&layout_path.with_extension("yaml"))) .map_err(|e| ConfigError::IoPath(e, layout_path.into()))?; let mut layout = String::new(); layout_file.read_to_string(&mut layout)?; - let layout: Layout = serde_yaml::from_str(&layout)?; + let layout: LayoutFromYaml = serde_yaml::from_str(&layout)?; + Ok(layout) } // It wants to use Path here, but that doesn't compile. #[allow(clippy::ptr_arg)] - pub fn from_dir(layout: &PathBuf, layout_dir: Option<&PathBuf>) -> LayoutResult { + pub fn from_dir(layout: &PathBuf, layout_dir: Option<&PathBuf>) -> LayoutFromYamlResult { match layout_dir { Some(dir) => Self::new(&dir.join(layout)) .or_else(|_| Self::from_default_assets(layout.as_path())), @@ -80,22 +99,21 @@ impl Layout { layout: Option<&PathBuf>, layout_path: Option<&PathBuf>, layout_dir: Option, - ) -> Option> { + ) -> Option { layout - .map(|p| Layout::from_dir(p, layout_dir.as_ref())) - .or_else(|| layout_path.map(|p| Layout::new(p))) + .map(|p| LayoutFromYaml::from_dir(p, layout_dir.as_ref())) + .or_else(|| layout_path.map(|p| LayoutFromYaml::new(p))) .or_else(|| { - Some(Layout::from_dir( + Some(LayoutFromYaml::from_dir( &std::path::PathBuf::from("default"), layout_dir.as_ref(), )) }) } - // Currently still needed but on nightly // this is already possible: // HashMap<&'static str, Vec> - pub fn from_default_assets(path: &Path) -> LayoutResult { + pub fn from_default_assets(path: &Path) -> LayoutFromYamlResult { match path.to_str() { Some("default") => Self::default_from_assets(), Some("strider") => Self::strider_from_assets(), @@ -109,24 +127,83 @@ impl Layout { // TODO Deserialize the assets from bytes &[u8], // once serde-yaml supports zero-copy - pub fn default_from_assets() -> LayoutResult { - let layout: Layout = + pub fn default_from_assets() -> LayoutFromYamlResult { + let layout: LayoutFromYaml = serde_yaml::from_str(String::from_utf8(setup::DEFAULT_LAYOUT.to_vec())?.as_str())?; Ok(layout) } - pub fn strider_from_assets() -> LayoutResult { - let layout: Layout = + pub fn strider_from_assets() -> LayoutFromYamlResult { + let layout: LayoutFromYaml = serde_yaml::from_str(String::from_utf8(setup::STRIDER_LAYOUT.to_vec())?.as_str())?; Ok(layout) } - pub fn disable_status_from_assets() -> LayoutResult { - let layout: Layout = + pub fn disable_status_from_assets() -> LayoutFromYamlResult { + let layout: LayoutFromYaml = serde_yaml::from_str(String::from_utf8(setup::NO_STATUS_LAYOUT.to_vec())?.as_str())?; Ok(layout) } +} +// The struct that carries the information template that is used to +// construct the layout +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(crate = "self::serde")] +pub struct LayoutTemplate { + pub direction: Direction, + #[serde(default)] + pub borderless: bool, + #[serde(default)] + pub parts: Vec, + #[serde(default)] + pub body: bool, + pub split_size: Option, + pub run: Option, +} + +impl LayoutTemplate { + // Insert an optional `[TabLayout]` at the correct postion + pub fn insert_tab_layout(mut self, tab_layout: Option) -> Self { + if self.body { + return tab_layout.unwrap_or_default().into(); + } + for (i, part) in self.parts.clone().iter().enumerate() { + if part.body { + self.parts.push(tab_layout.unwrap_or_default().into()); + self.parts.swap_remove(i); + break; + } + // recurse + let new_part = part.clone().insert_tab_layout(tab_layout.clone()); + self.parts.push(new_part); + self.parts.swap_remove(i); + } + self + } + + fn from_vec_tab_layout(tab_layout: Vec) -> Vec { + tab_layout + .iter() + .map(|tab_layout| Self::from(tab_layout.to_owned())) + .collect() + } +} + +// The tab-layout struct used to specify each individual tab. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(crate = "self::serde")] +pub struct TabLayout { + pub direction: Direction, + #[serde(default)] + pub borderless: bool, + #[serde(default)] + pub parts: Vec, + pub split_size: Option, + pub run: Option, +} + +impl Layout { pub fn total_terminal_panes(&self) -> usize { let mut total_panes = 0; total_panes += self.parts.len(); @@ -141,6 +218,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> { let mut run_instructions = vec![]; if self.parts.is_empty() { @@ -159,6 +244,28 @@ impl Layout { ) -> Vec<(Layout, PositionAndSize)> { split_space(space, self) } + + pub fn merge_tab_layout(&mut self, tab: TabLayout) { + self.parts.push(tab.into()); + } + + pub fn merge_layout_parts(&mut self, mut parts: Vec) { + self.parts.append(&mut parts); + } + + fn from_vec_tab_layout(tab_layout: Vec) -> Vec { + tab_layout + .iter() + .map(|tab_layout| Layout::from(tab_layout.to_owned())) + .collect() + } + + fn from_vec_template_layout(layout_template: Vec) -> Vec { + layout_template + .iter() + .map(|layout_template| Layout::from(layout_template.to_owned())) + .collect() + } } fn split_space_to_parts_vertically( @@ -168,7 +275,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 +299,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 +317,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 +340,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 +363,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 +382,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; } } @@ -313,3 +420,87 @@ fn split_space( } pane_positions } + +impl From for Layout { + fn from(tab: TabLayout) -> Self { + Layout { + direction: tab.direction, + borderless: tab.borderless, + parts: Self::from_vec_tab_layout(tab.parts), + split_size: tab.split_size, + run: tab.run, + } + } +} + +impl From for LayoutTemplate { + fn from(tab: TabLayout) -> Self { + Self { + direction: tab.direction, + borderless: tab.borderless, + parts: Self::from_vec_tab_layout(tab.parts), + body: false, + split_size: tab.split_size, + run: tab.run, + } + } +} + +impl From for Layout { + fn from(template: LayoutTemplate) -> Self { + Layout { + direction: template.direction, + borderless: template.borderless, + parts: Self::from_vec_template_layout(template.parts), + split_size: template.split_size, + run: template.run, + } + } +} + +impl Default for TabLayout { + fn default() -> Self { + Self { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: None, + run: None, + } + } +} + +impl Default for LayoutTemplate { + fn default() -> Self { + Self { + direction: Direction::Horizontal, + body: false, + borderless: false, + parts: vec![LayoutTemplate { + direction: Direction::Horizontal, + body: true, + borderless: false, + split_size: None, + run: None, + parts: vec![], + }], + split_size: None, + run: None, + } + } +} + +impl Default for LayoutFromYaml { + fn default() -> Self { + Self { + template: LayoutTemplate::default(), + borderless: false, + tabs: vec![], + } + } +} + +// The unit test location. +#[cfg(test)] +#[path = "./unit/layout_test.rs"] +mod layout_test; diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 746d586192..68aa9dd79b 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -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()), @@ -37,6 +38,7 @@ pub fn get_mode_info( ("x".to_string(), "Close".to_string()), ("r".to_string(), "Rename".to_string()), ("s".to_string(), "Sync".to_string()), + ("Tab".to_string(), "Toggle".to_string()), ], InputMode::Scroll => vec![ ("↓↑".to_string(), "Scroll".to_string()), diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs index a2f9231cd2..94994e1e3d 100644 --- a/zellij-utils/src/input/options.rs +++ b/zellij-utils/src/input/options.rs @@ -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, @@ -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, } } diff --git a/zellij-utils/src/input/unit/fixtures/layouts/deeply-nested-tab-layout.yaml b/zellij-utils/src/input/unit/fixtures/layouts/deeply-nested-tab-layout.yaml new file mode 100644 index 0000000000..6b5993732d --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/deeply-nested-tab-layout.yaml @@ -0,0 +1,41 @@ +--- +template: + direction: Horizontal + parts: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 21 + - direction: Vertical + split_size: + Percent: 79 + parts: + - direction: Horizontal + split_size: + Percent: 22 + - direction: Horizontal + split_size: + Percent: 78 + parts: + - direction: Horizontal + split_size: + Percent: 23 + - direction: Vertical + body: true + split_size: + Percent: 90 + - direction: Vertical + split_size: + Percent: 15 + - direction: Vertical + split_size: + Percent: 15 + - direction: Vertical + split_size: + Percent: 15 + +tabs: + - direction: Horizontal + split_size: + Percent: 24 diff --git a/zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-not-error.yaml b/zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-not-error.yaml new file mode 100644 index 0000000000..6537875918 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-not-error.yaml @@ -0,0 +1,18 @@ +--- +direction: Horizontal +parts: + - direction: Horizontal + parts: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 50 + - direction: Horizontal + tabs: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 diff --git a/zellij-utils/src/input/unit/fixtures/layouts/no-layout-template-specified.yaml b/zellij-utils/src/input/unit/fixtures/layouts/no-layout-template-specified.yaml new file mode 100644 index 0000000000..7794c9120f --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/no-layout-template-specified.yaml @@ -0,0 +1,6 @@ +--- +tabs: + - direction: Vertical + parts: + - direction: Horizontal + - direction: Horizontal diff --git a/zellij-utils/src/input/unit/fixtures/layouts/no-tab-section-specified.yaml b/zellij-utils/src/input/unit/fixtures/layouts/no-tab-section-specified.yaml new file mode 100644 index 0000000000..8b0f3daf12 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/no-tab-section-specified.yaml @@ -0,0 +1,6 @@ +--- +template: + direction: Horizontal + parts: + - direction: Horizontal + body: true diff --git a/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-command.yaml b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-command.yaml new file mode 100644 index 0000000000..9ad1034d43 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-command.yaml @@ -0,0 +1,35 @@ +--- +template: + direction: Horizontal + parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Horizontal + body: true + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar + +tabs: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 50 + - direction: Horizontal + parts: + - direction: Vertical + split_size: + Percent: 50 + run: + command: {cmd: htop} + - direction: Vertical + split_size: + Percent: 50 + run: + command: {cmd: htop, args: ["-C"]} diff --git a/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml new file mode 100644 index 0000000000..8804639579 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml @@ -0,0 +1,31 @@ +--- +template: + direction: Horizontal + parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Horizontal + body: true + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar + +tabs: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 50 + - direction: Horizontal + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 diff --git a/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab.yaml b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab.yaml new file mode 100644 index 0000000000..83f05076c5 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab.yaml @@ -0,0 +1,21 @@ +--- +template: + direction: Horizontal + parts: + - direction: Horizontal + body: true + +tabs: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 50 + - direction: Horizontal + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 diff --git a/zellij-utils/src/input/unit/fixtures/layouts/three-tabs-merged-correctly.yaml b/zellij-utils/src/input/unit/fixtures/layouts/three-tabs-merged-correctly.yaml new file mode 100644 index 0000000000..e2a09a739d --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/three-tabs-merged-correctly.yaml @@ -0,0 +1,29 @@ +--- +template: + direction: Vertical + parts: + - direction: Horizontal + body: true + - direction: Horizontal + + +tabs: + - direction: Horizontal + split_size: + Percent: 50 + - direction: Horizontal + split_size: + Percent: 50 + parts: + - direction: Horizontal + split_size: + Percent: 50 + - direction: Horizontal + - direction: Vertical + split_size: + Percent: 50 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Horizontal diff --git a/zellij-utils/src/input/unit/keybinds_test.rs b/zellij-utils/src/input/unit/keybinds_test.rs index 4767fc1573..e8a776d768 100644 --- a/zellij-utils/src/input/unit/keybinds_test.rs +++ b/zellij-utils/src/input/unit/keybinds_test.rs @@ -150,7 +150,7 @@ fn no_unbind_unbinds_none() { #[test] fn last_keybind_is_taken() { - let actions_1 = vec![Action::NoOp, Action::NewTab]; + let actions_1 = vec![Action::NoOp, Action::NewTab(None)]; let keyaction_1 = KeyActionFromYaml { action: actions_1.clone(), key: vec![Key::F(1), Key::Backspace, Key::Char('t')], @@ -171,7 +171,7 @@ fn last_keybind_is_taken() { #[test] fn last_keybind_overwrites() { - let actions_1 = vec![Action::NoOp, Action::NewTab]; + let actions_1 = vec![Action::NoOp, Action::NewTab(None)]; let keyaction_1 = KeyActionFromYaml { action: actions_1.clone(), key: vec![Key::F(1), Key::Backspace, Key::Char('t')], @@ -764,7 +764,7 @@ fn unbind_single_toplevel_multiple_keys_multiple_modes() { fn uppercase_and_lowercase_are_distinct() { let key_action_n = KeyActionFromYaml { key: vec![Key::Char('n')], - action: vec![Action::NewTab], + action: vec![Action::NewTab(None)], }; let key_action_large_n = KeyActionFromYaml { key: vec![Key::Char('N')], diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs new file mode 100644 index 0000000000..23c57b3d66 --- /dev/null +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -0,0 +1,696 @@ +use super::super::layout::*; + +fn layout_test_dir(layout: String) -> PathBuf { + let root = Path::new(env!("CARGO_MANIFEST_DIR")); + let layout_dir = root.join("src/input/unit/fixtures/layouts"); + layout_dir.join(layout) +} + +fn default_layout_dir(layout: String) -> PathBuf { + let root = Path::new(env!("CARGO_MANIFEST_DIR")); + let layout_dir = root.join("assets/layouts"); + layout_dir.join(layout) +} + +#[test] +fn default_layout_is_ok() { + let path = default_layout_dir("default.yaml".into()); + let layout = LayoutFromYaml::new(&path); + assert!(layout.is_ok()); +} + +#[test] +fn default_layout_has_one_tab() { + let path = default_layout_dir("default.yaml".into()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap(); + assert_eq!(layout_template.tabs.len(), 1); +} + +#[test] +fn default_layout_merged_correctly() { + let path = default_layout_dir("default.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.as_ref().unwrap(); + let tab_layout = layout_template + .template + .clone() + .insert_tab_layout(Some(layout_template.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Vertical, + borderless: true, + parts: vec![], + split_size: Some(SplitSize::Fixed(1)), + run: Some(Run::Plugin(Some("tab-bar".into()))), + }, + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: None, + run: None, + }, + Layout { + direction: Direction::Vertical, + borderless: true, + parts: vec![], + split_size: Some(SplitSize::Fixed(2)), + run: Some(Run::Plugin(Some("status-bar".into()))), + }, + ], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn default_layout_new_tab_correct() { + let path = default_layout_dir("default.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.as_ref().unwrap(); + let tab_layout = layout_template.template.clone().insert_tab_layout(None); + let merged_layout = Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Vertical, + borderless: true, + parts: vec![], + split_size: Some(SplitSize::Fixed(1)), + run: Some(Run::Plugin(Some("tab-bar".into()))), + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: None, + run: None, + }, + Layout { + direction: Direction::Vertical, + borderless: true, + parts: vec![], + split_size: Some(SplitSize::Fixed(2)), + run: Some(Run::Plugin(Some("status-bar".into()))), + }, + ], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn default_strider_layout_is_ok() { + let path = default_layout_dir("strider.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + assert!(layout_from_yaml.is_ok()); +} + +#[test] +fn default_disable_status_layout_is_ok() { + let path = default_layout_dir("disable-status-bar.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + assert!(layout_from_yaml.is_ok()); +} + +#[test] +fn default_disable_status_layout_has_no_tab() { + let path = default_layout_dir("disable-status-bar.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.as_ref().unwrap(); + assert_eq!(layout_template.tabs.len(), 0); +} + +#[test] +fn three_panes_with_tab_is_ok() { + let path = layout_test_dir("three-panes-with-tab.yaml".into()); + let layout = LayoutFromYaml::new(&path); + assert!(layout.is_ok()); +} + +#[test] +fn three_panes_with_tab_has_one_tab() { + let path = layout_test_dir("three-panes-with-tab.yaml".into()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap(); + assert_eq!(layout_template.tabs.len(), 1); +} + +#[test] +fn three_panes_with_tab_merged_correctly() { + let path = layout_test_dir("three-panes-with-tab.yaml".into()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap(); + let tab_layout = layout_template + .template + .clone() + .insert_tab_layout(Some(layout_template.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + ], + split_size: None, + run: None, + }, + ], + split_size: None, + run: None, + }], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn three_panes_with_tab_new_tab_is_correct() { + let path = layout_test_dir("three-panes-with-tab.yaml".into()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap(); + let tab_layout = layout_template.template.clone().insert_tab_layout(None); + let merged_layout = Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: None, + run: None, + }], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn three_panes_with_tab_and_default_plugins_is_ok() { + let path = layout_test_dir("three-panes-with-tab-and-default-plugins.yaml".into()); + let layout = LayoutFromYaml::new(&path); + assert!(layout.is_ok()); +} + +#[test] +fn three_panes_with_tab_and_default_plugins_has_one_tab() { + let path = layout_test_dir("three-panes-with-tab-and-default-plugins.yaml".into()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap(); + assert_eq!(layout_template.tabs.len(), 1); +} + +#[test] +fn three_panes_with_tab_and_default_plugins_merged_correctly() { + let path = layout_test_dir("three-panes-with-tab-and-default-plugins.yaml".into()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap(); + let tab_layout = layout_template + .template + .clone() + .insert_tab_layout(Some(layout_template.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Fixed(1)), + run: Some(Run::Plugin(Some("tab-bar".into()))), + }, + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + ], + split_size: None, + run: None, + }, + ], + split_size: None, + run: None, + }, + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Fixed(2)), + run: Some(Run::Plugin(Some("status-bar".into()))), + }, + ], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() { + let path = layout_test_dir("three-panes-with-tab-and-default-plugins.yaml".into()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap(); + let tab_layout = layout_template.template.clone().insert_tab_layout(None); + let merged_layout = Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Fixed(1)), + run: Some(Run::Plugin(Some("tab-bar".into()))), + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: None, + run: None, + }, + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Fixed(2)), + run: Some(Run::Plugin(Some("status-bar".into()))), + }, + ], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn deeply_nested_tab_is_ok() { + let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); + let layout = LayoutFromYaml::new(&path); + assert!(layout.is_ok()); +} + +#[test] +fn deeply_nested_tab_has_one_tab() { + let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap(); + assert_eq!(layout_template.tabs.len(), 1); +} + +#[test] +fn deeply_nested_tab_merged_correctly() { + let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap(); + let tab_layout = layout_template + .template + .clone() + .insert_tab_layout(Some(layout_template.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(21)), + run: None, + }, + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(22)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(23)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(24)), + run: None, + }, + ], + split_size: Some(SplitSize::Percent(78)), + run: None, + }, + ], + split_size: Some(SplitSize::Percent(79)), + run: None, + }, + ], + split_size: Some(SplitSize::Percent(90)), + run: None, + }, + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(15)), + run: None, + }, + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(15)), + run: None, + }, + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(15)), + run: None, + }, + ], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn three_tabs_is_ok() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + assert!(layout_from_yaml.is_ok()); +} + +#[test] +fn three_tabs_has_three_tabs() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.unwrap(); + assert_eq!(layout_template.tabs.len(), 3); +} + +#[test] +fn three_tabs_tab_one_merged_correctly() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.as_ref().unwrap(); + let tab_layout = layout_template + .template + .clone() + .insert_tab_layout(Some(layout_template.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: None, + run: None, + }, + ], + split_size: None, + run: None, + }; + + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn three_tabs_tab_two_merged_correctly() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.as_ref().unwrap(); + let tab_layout = layout_template + .template + .clone() + .insert_tab_layout(Some(layout_template.tabs[1].clone())); + let merged_layout = Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: None, + run: None, + }, + ], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: None, + run: None, + }, + ], + split_size: None, + run: None, + }; + + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn three_tabs_tab_three_merged_correctly() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap(); + let tab_layout = layout_template + .template + .clone() + .insert_tab_layout(Some(layout_template.tabs[2].clone())); + let merged_layout = Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![ + Layout { + direction: Direction::Vertical, + borderless: false, + parts: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: None, + run: None, + }, + ], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: None, + run: None, + }, + ], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn no_tabs_is_ok() { + let path = layout_test_dir("no-tab-section-specified.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + assert!(layout_from_yaml.is_ok()); +} + +#[test] +fn no_tabs_has_no_tabs() { + let path = layout_test_dir("no-tab-section-specified.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.unwrap(); + assert_eq!(layout_template.tabs.len(), 0); +} + +#[test] +fn no_tabs_merged_correctly() { + let path = layout_test_dir("no-tab-section-specified.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.as_ref().unwrap(); + let tab_layout = layout_template.template.clone().insert_tab_layout(None); + let merged_layout = Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![Layout { + direction: Direction::Horizontal, + borderless: false, + parts: vec![], + split_size: None, + run: None, + }], + split_size: None, + run: None, + }; + + assert_eq!(merged_layout, tab_layout.into()); +} + +#[test] +fn no_layout_template_specified_is_ok() { + let path = layout_test_dir("no-layout-template-specified.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + assert!(layout_from_yaml.is_ok()); +} + +#[test] +fn no_layout_template_has_one_tab() { + let path = layout_test_dir("no-layout-template-specified.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.unwrap(); + assert_eq!(layout_template.tabs.len(), 1); +} + +#[test] +fn no_layout_template_merged_correctly() { + let path = layout_test_dir("no-layout-template-specified.yaml".into()); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.as_ref().unwrap(); + let tab_layout = layout_template + .template + .clone() + .insert_tab_layout(Some(layout_template.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Horizontal, + parts: vec![Layout { + direction: Direction::Vertical, + parts: vec![ + Layout { + direction: Direction::Horizontal, + parts: vec![], + split_size: None, + run: None, + borderless: false, + }, + Layout { + direction: Direction::Horizontal, + parts: vec![], + split_size: None, + run: None, + borderless: false, + }, + ], + split_size: None, + run: None, + borderless: false, + }], + split_size: None, + run: None, + borderless: false, + }; + + assert_eq!(merged_layout, tab_layout.into()); +} diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index cf952bdcf8..009d99a3d4 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -1,18 +1,20 @@ //! IPC stuff for starting to split things into a client and server model. -use crate::cli::CliArgs; -use crate::pane_size::PositionAndSize; use crate::{ + cli::CliArgs, errors::{get_current_ctx, ErrorContext}, - input::{actions::Action, layout::Layout, options::Options}, + input::{actions::Action, layout::LayoutFromYaml, options::Options}, + pane_size::PositionAndSize, }; use interprocess::local_socket::LocalSocketStream; use nix::unistd::dup; use serde::{Deserialize, Serialize}; -use std::fmt::{Display, Error, Formatter}; -use std::io::{self, Write}; -use std::marker::PhantomData; -use std::os::unix::io::{AsRawFd, FromRawFd}; +use std::{ + fmt::{Display, Error, Formatter}, + io::{self, Write}, + marker::PhantomData, + os::unix::io::{AsRawFd, FromRawFd}, +}; use zellij_tile::data::Palette; @@ -56,7 +58,12 @@ pub enum ClientToServerMsg { // Disconnect from the session we're connected to DisconnectFromSession,*/ TerminalResize(PositionAndSize), - NewClient(ClientAttributes, Box, Box, Option), + NewClient( + ClientAttributes, + Box, + Box, + Option, + ), AttachClient(ClientAttributes, bool, Options), Action(Action), ClientExited, diff --git a/zellij-utils/src/pane_size.rs b/zellij-utils/src/pane_size.rs index da165d4e6e..db439b6126 100644 --- a/zellij-utils/src/pane_size.rs +++ b/zellij-utils/src/pane_size.rs @@ -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 + } } diff --git a/zellij-utils/src/setup.rs b/zellij-utils/src/setup.rs index e396095134..0d9855abaf 100644 --- a/zellij-utils/src/setup.rs +++ b/zellij-utils/src/setup.rs @@ -1,12 +1,13 @@ -use crate::consts::{ - FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION, ZELLIJ_PROJ_DIR, -}; -use crate::input::options::Options; use crate::{ cli::{CliArgs, Command}, + consts::{ + FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION, + ZELLIJ_PROJ_DIR, + }, input::{ config::{Config, ConfigError}, - layout::Layout, + layout::LayoutFromYaml, + options::Options, }, }; use directories_next::BaseDirs; @@ -150,7 +151,9 @@ impl Setup { /// file options: /// 1. command line options (`zellij options`) /// 2. config options (`config.yaml`) - pub fn from_options(opts: &CliArgs) -> Result<(Config, Option, Options), ConfigError> { + pub fn from_options( + opts: &CliArgs, + ) -> Result<(Config, Option, Options), ConfigError> { let clean = match &opts.command { Some(Command::Setup(ref setup)) => setup.clean, _ => false, @@ -160,7 +163,6 @@ impl Setup { match Config::try_from(opts) { Ok(config) => config, Err(e) => { - eprintln!("There was an error in the config file:"); return Err(e); } } @@ -174,7 +176,7 @@ impl Setup { .layout_dir .clone() .or_else(|| get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir))); - let layout_result = crate::input::layout::Layout::from_path_or_default( + let layout_result = LayoutFromYaml::from_path_or_default( opts.layout.as_ref(), opts.layout_path.as_ref(), layout_dir, @@ -183,10 +185,10 @@ impl Setup { None => None, Some(Ok(layout)) => Some(layout), Some(Err(e)) => { - eprintln!("There was an error in the layout file:"); return Err(e); } }; + //.map(|layout| layout.template); if let Some(Command::Setup(ref setup)) = &opts.command { setup.from_cli(opts, &config_options).map_or_else( diff --git a/zellij-utils/src/shared.rs b/zellij-utils/src/shared.rs index a741cc10b2..b1edb97fd0 100644 --- a/zellij-utils/src/shared.rs +++ b/zellij-utils/src/shared.rs @@ -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()