From 5ede25dc37ceb192032524ac9200bb1ca95e5863 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Fri, 9 Jul 2021 23:37:36 +0200 Subject: [PATCH 1/6] Add `tabs` to `layouts` fixes #603, fixes #349 * The layout has now a unique `tabs` section, that can be used, like the `parts` section, everything that is not inside the tabs section is assumed to be present on every single tab that is opened. This is a BREAKING CHANGE for people that use custom `layouts` already, since the `tabs` section is not optional - for clarity and intentionality reasons. The functionality to specify multiple tabs is already there, but is still gated behind a panic, until #621 is fixed. So for now one tab can be specified to load on startup. * The `NewTab` action can optionally be bound to open a layout that is assumed to be in the new `tabs` section This is a BREAKING CHANGE for people that have the `NewTab` action already bound in the config file: ``` - action: [NewTab, ] key: [F: 5,] ``` must now be specified as: ``` - action: [NewTab: ,] key: [F: 5,] ``` Optionally a layout that should be opened on the new tab can be specified: ``` - action: [NewTab: { direction: Vertical, parts: [ {direction: Horizontal, split_size: {Percent: 50}}, {direction: Horizontal, run: {command: {cmd: "htop"}}},], key: [F: 6,] ``` or: ``` - action: [NewTab: {direction: Vertical, run: {command: {cmd: "htop"} }},] key: [F: 7,] ``` or ``` - action: [NewTab: { direction: Vertical, parts: [ {direction: Vertical, split_size: {Percent: 25},run: {plugin: "strider" }}, {direction: Horizontal}],}, MoveFocus: Left,] key: [F: 8,] ``` --- src/main.rs | 3 +- zellij-client/src/input_handler.rs | 2 +- zellij-client/src/lib.rs | 4 +- zellij-server/src/lib.rs | 60 +- zellij-server/src/pty.rs | 15 +- zellij-server/src/route.rs | 4 +- zellij-utils/assets/config/default.yaml | 2 +- zellij-utils/assets/layouts/default.yaml | 2 + .../assets/layouts/disable-status-bar.yaml | 2 + zellij-utils/assets/layouts/strider.yaml | 16 +- zellij-utils/src/input/actions.rs | 7 +- zellij-utils/src/input/layout.rs | 186 +++++- .../layouts/deeply-nested-tab-layout.yaml | 40 ++ .../layouts/multiple-tabs-should-panic.yaml | 18 + .../layouts/no-tabs-should-panic.yaml | 18 + .../tabs-and-parts-together-should-panic.yaml | 25 + .../three-panes-with-tab-and-command.yaml | 32 ++ ...ee-panes-with-tab-and-default-plugins.yaml | 28 + .../layouts/three-panes-with-tab.yaml | 18 + zellij-utils/src/input/unit/keybinds_test.rs | 6 +- zellij-utils/src/input/unit/layout_test.rs | 538 ++++++++++++++++++ zellij-utils/src/ipc.rs | 23 +- 22 files changed, 993 insertions(+), 56 deletions(-) create mode 100644 zellij-utils/src/input/unit/fixtures/layouts/deeply-nested-tab-layout.yaml create mode 100644 zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-panic.yaml create mode 100644 zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-panic.yaml create mode 100644 zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-panic.yaml create mode 100644 zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-command.yaml create mode 100644 zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml create mode 100644 zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab.yaml create mode 100644 zellij-utils/src/input/unit/layout_test.rs diff --git a/src/main.rs b/src/main.rs index b3706ead..050c0cca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -94,7 +94,8 @@ pub fn main() { opts.layout.as_ref(), opts.layout_path.as_ref(), layout_dir, - ); + ) + .map(|layout| layout.construct_main_layout()); start_client( Box::new(os_input), diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index f02b5ad8..19b0f629 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -183,7 +183,7 @@ impl InputHandler { } Action::CloseFocus | Action::NewPane(_) - | Action::NewTab + | Action::NewTab(_) | Action::GoToNextTab | Action::GoToPreviousTab | Action::CloseTab diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 2bfaae2e..8bb595bc 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -18,7 +18,7 @@ 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, layout::MainLayout, options::Options}, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, }; @@ -86,7 +86,7 @@ pub fn start_client( opts: CliArgs, config: Config, info: ClientInfo, - layout: Option, + layout: Option, ) { 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"; let take_snapshot = "\u{1b}[?1049h"; diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 660acdc9..2dfe6de7 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -9,11 +9,11 @@ mod thread_bus; mod ui; mod wasm_vm; -use zellij_utils::zellij_tile; - -use std::path::PathBuf; -use std::sync::{Arc, Mutex, RwLock}; -use std::thread; +use std::{ + path::PathBuf, + sync::{Arc, Mutex, RwLock}, + thread, +}; use wasmer::Store; use zellij_tile::data::{Event, Palette, PluginCapabilities}; @@ -32,17 +32,23 @@ use zellij_utils::{ input::{ command::{RunCommand, TerminalAction}, get_mode_info, - layout::Layout, + layout::MainLayout, options::Options, }, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, setup::get_default_data_dir, + zellij_tile, }; /// 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, @@ -204,7 +210,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; @@ -216,14 +222,34 @@ 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())); + // Spawning tabs in too quick succession might mess up the layout + // TODO: investigate why + thread::sleep(std::time::Duration::from_millis(250)); + } + } else { + spawn_tabs(None); + } + } + } } ServerInstruction::AttachClient(attrs, _, options) => { *session_state.write().unwrap() = SessionState::Attached; @@ -299,7 +325,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/pty.rs b/zellij-server/src/pty.rs index 5ef3b321..5f01ca27 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, MainLayout, 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.construct_tab_layout(tab_layout); + pty.spawn_terminals_for_layout(merged_layout, 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 22d843f0..47eeabad 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -175,11 +175,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-utils/assets/config/default.yaml b/zellij-utils/assets/config/default.yaml index d9090a8d..e90b60a1 100644 --- a/zellij-utils/assets/config/default.yaml +++ b/zellij-utils/assets/config/default.yaml @@ -134,7 +134,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',] diff --git a/zellij-utils/assets/layouts/default.yaml b/zellij-utils/assets/layouts/default.yaml index 96bf1809..39dbdc1b 100644 --- a/zellij-utils/assets/layouts/default.yaml +++ b/zellij-utils/assets/layouts/default.yaml @@ -7,6 +7,8 @@ parts: run: plugin: tab-bar - direction: Vertical + tabs: + - direction: Vertical - direction: Vertical split_size: Fixed: 2 diff --git a/zellij-utils/assets/layouts/disable-status-bar.yaml b/zellij-utils/assets/layouts/disable-status-bar.yaml index b990ba50..a0a59239 100644 --- a/zellij-utils/assets/layouts/disable-status-bar.yaml +++ b/zellij-utils/assets/layouts/disable-status-bar.yaml @@ -7,3 +7,5 @@ parts: run: plugin: tab-bar - direction: Vertical + tabs: + - direction: Vertical diff --git a/zellij-utils/assets/layouts/strider.yaml b/zellij-utils/assets/layouts/strider.yaml index 9bbe5772..a39f327c 100644 --- a/zellij-utils/assets/layouts/strider.yaml +++ b/zellij-utils/assets/layouts/strider.yaml @@ -7,13 +7,15 @@ parts: run: plugin: tab-bar - direction: Vertical - parts: - - direction: Horizontal - split_size: - Percent: 20 - run: - plugin: strider - - direction: Horizontal + tabs: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 20 + run: + plugin: strider + - direction: Horizontal - direction: Vertical split_size: Fixed: 2 diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index bfd7d255..ab711c75 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 { @@ -61,8 +62,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. diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index a48c3521..713605e9 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,16 +43,58 @@ pub enum Run { Command(RunCommand), } -#[derive(Debug, Serialize, Deserialize, Clone)] +// The layout struct that is ultimately used to build the layouts +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(crate = "self::serde")] pub struct Layout { pub direction: Direction, #[serde(default)] pub parts: Vec, + #[serde(default)] + pub tabs: Vec, pub split_size: Option, pub run: Option, } +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(crate = "self::serde")] +pub struct TabLayout { + pub direction: Direction, + #[serde(default)] + pub parts: Vec, + pub split_size: Option, + pub run: Option, +} + +// Main layout struct, that carries information based on +// position of tabs +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(crate = "self::serde")] +pub struct MainLayout { + pub pre_tab: Layout, + pub post_tab: Vec, + pub tabs: Vec, +} + +impl MainLayout { + pub fn construct_tab_layout(&self, tab_layout: Option) -> Layout { + if let Some(tab_layout) = tab_layout { + let mut pre_tab_layout = self.pre_tab.clone(); + let post_tab_layout = &self.post_tab; + pre_tab_layout.merge_tab_layout(tab_layout); + pre_tab_layout.merge_layout_parts(post_tab_layout.to_owned()); + pre_tab_layout + } else { + let mut pre_tab_layout = self.pre_tab.clone(); + let post_tab_layout = &self.post_tab; + let default_tab_layout = TabLayout::default(); + pre_tab_layout.merge_tab_layout(default_tab_layout); + pre_tab_layout.merge_layout_parts(post_tab_layout.to_owned()); + pre_tab_layout + } + } +} + type LayoutResult = Result; impl Layout { @@ -168,6 +211,113 @@ impl Layout { ) -> Vec<(Layout, PositionAndSize)> { split_space(space, self) } + + // Split the layout into parts that can be reassebled per tab + // returns the layout pre tab, the parts post tab and the tab layouts + pub fn split_main_and_tab_layout(&self) -> (Layout, Vec, Vec) { + let mut main_layout = self.clone(); + let mut pre_tab_layout = self.clone(); + let mut post_tab_layout = vec![]; + let mut tabs = vec![]; + let mut post_tab = false; + + pre_tab_layout.parts.clear(); + pre_tab_layout.tabs.clear(); + + if !main_layout.tabs.is_empty() { + tabs.append(&mut main_layout.tabs); + post_tab = true; + } + + for part in main_layout.parts.drain(..) { + let (curr_pre_layout, mut curr_post_layout, mut curr_tabs) = + part.split_main_and_tab_layout(); + + // Leaf + if !post_tab && part.tabs.is_empty() { + pre_tab_layout.parts.push(curr_pre_layout); + } + + // Todo: Convert into actual Error, or use the future logging system. + if !part.tabs.is_empty() && !part.parts.is_empty() { + panic!("Tabs and Parts need to be specified separately."); + } + + // Todo: Convert into actual Error, or use the future logging system. + if (!part.tabs.is_empty() || !curr_tabs.is_empty()) && post_tab { + panic!("Only one tab section should be specified."); + } + + // Node + if !part.tabs.is_empty() { + tabs.append(&mut part.tabs.clone()); + post_tab = true; + // Node + } else if !curr_tabs.is_empty() { + tabs.append(&mut curr_tabs); + post_tab = true; + // Leaf + } else if post_tab { + if curr_post_layout.is_empty() { + let mut part_no_tab = part.clone(); + part_no_tab.tabs.clear(); + part_no_tab.parts.clear(); + post_tab_layout.push(part_no_tab); + } else { + post_tab_layout.append(&mut curr_post_layout); + } + } + } + (pre_tab_layout, post_tab_layout, tabs) + } + + 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); + } + + pub fn construct_full_layout(&self, tab_layout: Option) -> Self { + if let Some(tab_layout) = tab_layout { + let (mut pre_tab_layout, post_tab_layout, _) = self.split_main_and_tab_layout(); + pre_tab_layout.merge_tab_layout(tab_layout); + pre_tab_layout.merge_layout_parts(post_tab_layout); + pre_tab_layout + } else { + let (mut pre_tab_layout, post_tab_layout, _) = self.split_main_and_tab_layout(); + let default_tab_layout = TabLayout::default(); + pre_tab_layout.merge_tab_layout(default_tab_layout); + pre_tab_layout.merge_layout_parts(post_tab_layout); + pre_tab_layout + } + } + + pub fn construct_main_layout(&self) -> MainLayout { + let (pre_tab, post_tab, tabs) = self.split_main_and_tab_layout(); + + if tabs.is_empty() { + panic!("The layout file should have a `tabs` section specified"); + } + + if tabs.len() > 1 { + panic!("The layout file should have one single tab in the `tabs` section specified"); + } + + MainLayout { + pre_tab, + post_tab, + tabs, + } + } + + fn from_vec_tab_layout(tab_layout: Vec) -> Vec { + tab_layout + .iter() + .map(|tab_layout| Layout::from(tab_layout.to_owned())) + .collect() + } } fn split_space_to_parts_vertically( @@ -322,3 +472,31 @@ fn split_space( } pane_positions } + +impl From for Layout { + fn from(tab: TabLayout) -> Self { + Layout { + direction: tab.direction, + parts: Layout::from_vec_tab_layout(tab.parts), + tabs: vec![], + split_size: tab.split_size, + run: tab.run, + } + } +} + +impl Default for TabLayout { + fn default() -> Self { + Self { + direction: Direction::Horizontal, + parts: vec![], + split_size: None, + run: None, + } + } +} + +// The unit test location. +#[cfg(test)] +#[path = "./unit/layout_test.rs"] +mod layout_test; 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 00000000..11e5323e --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/deeply-nested-tab-layout.yaml @@ -0,0 +1,40 @@ +--- +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 + split_size: + Percent: 77 + tabs: + - direction: Horizontal + split_size: + Percent: 24 + split_size: + Percent: 90 + - direction: Vertical + split_size: + Percent: 15 + - direction: Vertical + split_size: + Percent: 15 + - direction: Vertical + split_size: + Percent: 15 diff --git a/zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-panic.yaml b/zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-panic.yaml new file mode 100644 index 00000000..65378759 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-panic.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-tabs-should-panic.yaml b/zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-panic.yaml new file mode 100644 index 00000000..a67b0ff9 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-panic.yaml @@ -0,0 +1,18 @@ +--- +direction: Horizontal +parts: + - direction: Horizontal + parts: + - 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/tabs-and-parts-together-should-panic.yaml b/zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-panic.yaml new file mode 100644 index 00000000..1ef5a05e --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-panic.yaml @@ -0,0 +1,25 @@ +--- +direction: Horizontal +parts: + - direction: Horizontal + 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 + tabs: + - 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-and-command.yaml b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-command.yaml new file mode 100644 index 00000000..34de4291 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-command.yaml @@ -0,0 +1,32 @@ +--- +direction: Horizontal +parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Horizontal + 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"]} + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar 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 00000000..a803f291 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml @@ -0,0 +1,28 @@ +--- +direction: Horizontal +parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Horizontal + 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 + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar 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 00000000..e0f25b92 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab.yaml @@ -0,0 +1,18 @@ +--- +direction: Horizontal +parts: + - direction: Horizontal + 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/keybinds_test.rs b/zellij-utils/src/input/unit/keybinds_test.rs index 4767fc15..e8a776d7 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 00000000..95de356c --- /dev/null +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -0,0 +1,538 @@ +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 = Layout::new(&path); + assert!(layout.is_ok()); +} + +#[test] +fn default_layout_has_one_tab() { + let path = default_layout_dir("default.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + assert_eq!(main_layout.tabs.len(), 1); +} + +#[test] +fn default_layout_has_one_pre_tab() { + let path = default_layout_dir("default.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + assert_eq!(main_layout.pre_tab.parts.len(), 1); +} + +#[test] +fn default_layout_has_one_post_tab() { + let path = default_layout_dir("default.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + assert_eq!(main_layout.post_tab.len(), 1); +} + +#[test] +fn default_layout_merged_correctly() { + let path = default_layout_dir("default.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Horizontal, + parts: vec![ + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Fixed(1)), + run: Some(Run::Plugin(Some("tab-bar".into()))), + }, + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: None, + run: None, + }, + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Fixed(2)), + run: Some(Run::Plugin(Some("status-bar".into()))), + }, + ], + tabs: vec![], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout); +} + +#[test] +fn default_layout_new_tab_correct() { + let path = default_layout_dir("default.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let tab_layout = main_layout.construct_tab_layout(None); + let merged_layout = Layout { + direction: Direction::Horizontal, + parts: vec![ + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Fixed(1)), + run: Some(Run::Plugin(Some("tab-bar".into()))), + }, + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: None, + run: None, + }, + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Fixed(2)), + run: Some(Run::Plugin(Some("status-bar".into()))), + }, + ], + tabs: vec![], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout); +} + +#[test] +fn default_strider_layout_is_ok() { + let path = default_layout_dir("strider.yaml".into()); + let layout = Layout::new(&path); + assert!(layout.is_ok()); +} + +#[test] +fn default_disable_status_layout_is_ok() { + let path = default_layout_dir("disable-status-bar.yaml".into()); + let layout = Layout::new(&path); + assert!(layout.is_ok()); +} + +#[test] +fn default_disable_status_layout_has_one_tab() { + let path = default_layout_dir("disable-status-bar.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + assert_eq!(main_layout.tabs.len(), 1); +} + +#[test] +fn default_disable_status_layout_has_one_pre_tab() { + let path = default_layout_dir("disable-status-bar.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + assert_eq!(main_layout.pre_tab.parts.len(), 1); +} + +#[test] +fn default_disable_status_layout_has_no_post_tab() { + let path = default_layout_dir("disable-status-bar.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + assert!(main_layout.post_tab.is_empty()); +} + +#[test] +fn three_panes_with_tab_is_ok() { + let path = layout_test_dir("three-panes-with-tab.yaml".into()); + let layout = Layout::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 = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout(); + assert_eq!(main_layout.tabs.len(), 1); +} + +#[test] +fn three_panes_with_tab_no_post_tab() { + let path = layout_test_dir("three-panes-with-tab.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout(); + assert!(main_layout.post_tab.is_empty()); +} + +#[test] +fn three_panes_with_tab_no_pre_tab() { + let path = layout_test_dir("three-panes-with-tab.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout(); + assert!(main_layout.pre_tab.parts.is_empty()); +} + +#[test] +fn three_panes_with_tab_merged_correctly() { + let path = layout_test_dir("three-panes-with-tab.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Horizontal, + parts: vec![Layout { + direction: Direction::Vertical, + parts: vec![ + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + parts: vec![ + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + ], + tabs: vec![], + split_size: None, + run: None, + }, + ], + tabs: vec![], + split_size: None, + run: None, + }], + tabs: vec![], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout); +} + +#[test] +fn three_panes_with_tab_new_tab_is_correct() { + let path = layout_test_dir("three-panes-with-tab.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let tab_layout = main_layout.construct_tab_layout(None); + let merged_layout = Layout { + direction: Direction::Horizontal, + parts: vec![Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: None, + run: None, + }], + tabs: vec![], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout); +} + +#[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 = Layout::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 = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout(); + assert_eq!(main_layout.tabs.len(), 1); +} + +#[test] +fn three_panes_with_tab_and_default_plugins_one_post_tab() { + let path = layout_test_dir("three-panes-with-tab-and-default-plugins.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout(); + assert_eq!(main_layout.post_tab.len(), 1); +} + +#[test] +fn three_panes_with_tab_and_default_plugins_has_pre_tab() { + let path = layout_test_dir("three-panes-with-tab-and-default-plugins.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout(); + assert!(!main_layout.pre_tab.parts.is_empty()); +} + +#[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 = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Horizontal, + parts: vec![ + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Fixed(1)), + run: Some(Run::Plugin(Some("tab-bar".into()))), + }, + Layout { + direction: Direction::Vertical, + parts: vec![ + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + parts: vec![ + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + ], + tabs: vec![], + split_size: None, + run: None, + }, + ], + tabs: vec![], + split_size: None, + run: None, + }, + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Fixed(2)), + run: Some(Run::Plugin(Some("status-bar".into()))), + }, + ], + tabs: vec![], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout); +} + +#[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 = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let tab_layout = main_layout.construct_tab_layout(None); + let merged_layout = Layout { + direction: Direction::Horizontal, + parts: vec![ + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Fixed(1)), + run: Some(Run::Plugin(Some("tab-bar".into()))), + }, + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: None, + run: None, + }, + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Fixed(2)), + run: Some(Run::Plugin(Some("status-bar".into()))), + }, + ], + tabs: vec![], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout); +} + +#[test] +fn deeply_nested_tab_is_ok() { + let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); + let layout = Layout::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 = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout(); + assert_eq!(main_layout.tabs.len(), 1); +} + +#[test] +fn deeply_nested_tab_three_post_tab() { + let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout(); + assert_eq!(main_layout.post_tab.len(), 3); +} + +#[test] +fn deeply_nested_tab_has_many_pre_tab() { + let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout(); + assert!(!main_layout.pre_tab.parts.is_empty()); +} + +#[test] +fn deeply_nested_tab_merged_correctly() { + let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Horizontal, + parts: vec![ + Layout { + direction: Direction::Vertical, + parts: vec![ + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(21)), + run: None, + }, + Layout { + direction: Direction::Vertical, + parts: vec![ + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(22)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + parts: vec![Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(23)), + run: None, + }], + tabs: vec![], + split_size: Some(SplitSize::Percent(78)), + run: None, + }, + ], + tabs: vec![], + split_size: Some(SplitSize::Percent(79)), + run: None, + }, + ], + tabs: vec![], + split_size: Some(SplitSize::Percent(90)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(24)), + run: None, + }, + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(15)), + run: None, + }, + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(15)), + run: None, + }, + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(15)), + run: None, + }, + ], + tabs: vec![], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout); +} + +#[test] +#[should_panic] +// TODO Make error out of this +fn no_tabs_specified_should_panic() { + let path = layout_test_dir("no-tabs-should-panic.yaml".into()); + let layout = Layout::new(&path); + let _main_layout = layout.unwrap().construct_main_layout(); +} + +#[test] +#[should_panic] +// TODO Make error out of this +// Only untill #631 is fixed +fn multiple_tabs_specified_should_panic() { +let path = layout_test_dir("multiple-tabs-should-panic.yaml".into()); +let layout = Layout::new(&path); +let _main_layout = layout.unwrap().construct_main_layout(); +} diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index cf952bdc..18910901 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::MainLayout, 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, From f323880fb7129cd74b9d156e9688fac9654480b5 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Fri, 23 Jul 2021 17:25:05 +0200 Subject: [PATCH 2/6] !fixup cargo fmt --- zellij-server/src/lib.rs | 2 +- zellij-utils/src/input/layout.rs | 16 ++++++++-------- zellij-utils/src/input/unit/layout_test.rs | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 6ea9f228..d90301cc 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -10,12 +10,12 @@ mod thread_bus; mod ui; mod wasm_vm; +use log::info; use std::{ path::PathBuf, sync::{Arc, Mutex, RwLock}, thread, }; -use log::info; use zellij_utils::zellij_tile; use wasmer::Store; diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 713605e9..26d96764 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -258,14 +258,14 @@ impl Layout { post_tab = true; // Leaf } else if post_tab { - if curr_post_layout.is_empty() { - let mut part_no_tab = part.clone(); - part_no_tab.tabs.clear(); - part_no_tab.parts.clear(); - post_tab_layout.push(part_no_tab); - } else { - post_tab_layout.append(&mut curr_post_layout); - } + if curr_post_layout.is_empty() { + let mut part_no_tab = part.clone(); + part_no_tab.tabs.clear(); + part_no_tab.parts.clear(); + post_tab_layout.push(part_no_tab); + } else { + post_tab_layout.append(&mut curr_post_layout); + } } } (pre_tab_layout, post_tab_layout, tabs) diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index 95de356c..89217e04 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -532,7 +532,7 @@ fn no_tabs_specified_should_panic() { // TODO Make error out of this // Only untill #631 is fixed fn multiple_tabs_specified_should_panic() { -let path = layout_test_dir("multiple-tabs-should-panic.yaml".into()); -let layout = Layout::new(&path); -let _main_layout = layout.unwrap().construct_main_layout(); + let path = layout_test_dir("multiple-tabs-should-panic.yaml".into()); + let layout = Layout::new(&path); + let _main_layout = layout.unwrap().construct_main_layout(); } From 806ffad5539c99380e7c0e423f4c864d8db431e2 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Fri, 23 Jul 2021 17:44:05 +0200 Subject: [PATCH 3/6] fixup! fix e2e fixtures --- .../parts-total-less-than-100-percent.yaml | 2 ++ .../parts-total-more-than-100-percent.yaml | 3 +++ .../layouts/three-panes-with-nesting.yaml | 24 ++++++++++--------- 3 files changed, 18 insertions(+), 11 deletions(-) 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 e1a1a607..f0f66a32 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 @@ -9,6 +9,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 33d94225..2b55547d 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 @@ -9,6 +9,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 f1e0dd7e..20a647f3 100644 --- a/src/tests/fixtures/layouts/three-panes-with-nesting.yaml +++ b/src/tests/fixtures/layouts/three-panes-with-nesting.yaml @@ -1,16 +1,18 @@ --- 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 From 9a5b6690af47236c16004a0ee4655500bad038a6 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Sun, 1 Aug 2021 20:27:43 +0200 Subject: [PATCH 4/6] Add example tab layouts --- example/multiple_tabs_layout.yaml | 81 ++++++++++++++++++ .../multiple_tabs_layout_htop_command.yaml | 85 +++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 example/multiple_tabs_layout.yaml create mode 100644 example/multiple_tabs_layout_htop_command.yaml diff --git a/example/multiple_tabs_layout.yaml b/example/multiple_tabs_layout.yaml new file mode 100644 index 00000000..40689a96 --- /dev/null +++ b/example/multiple_tabs_layout.yaml @@ -0,0 +1,81 @@ +--- +direction: Horizontal +parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Vertical + tabs: + - 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: 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 + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar diff --git a/example/multiple_tabs_layout_htop_command.yaml b/example/multiple_tabs_layout_htop_command.yaml new file mode 100644 index 00000000..6739eba8 --- /dev/null +++ b/example/multiple_tabs_layout_htop_command.yaml @@ -0,0 +1,85 @@ +--- +direction: Horizontal +parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Vertical + 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 + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar From 2e1775678577e8587ca0830a15810212c4b209f7 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 2 Aug 2021 12:03:42 +0200 Subject: [PATCH 5/6] Change layout panics to errors * Adjust and add tests for the change --- example/multiple_tabs_layout.yaml | 1 + zellij-utils/src/input/config.rs | 92 ++++++++ zellij-utils/src/input/layout.rs | 51 +++-- ...ml => multiple-tabs-should-not-error.yaml} | 0 ...d-panic.yaml => no-tabs-should-error.yaml} | 0 .../tabs-and-parts-together-should-error.yaml | 17 ++ .../tabs-and-parts-together-should-panic.yaml | 25 -- .../layouts/three-tabs-merged-correctly.yaml | 25 ++ zellij-utils/src/input/unit/layout_test.rs | 215 +++++++++++++++--- zellij-utils/src/setup.rs | 12 +- 10 files changed, 354 insertions(+), 84 deletions(-) rename zellij-utils/src/input/unit/fixtures/layouts/{multiple-tabs-should-panic.yaml => multiple-tabs-should-not-error.yaml} (100%) rename zellij-utils/src/input/unit/fixtures/layouts/{no-tabs-should-panic.yaml => no-tabs-should-error.yaml} (100%) create mode 100644 zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-error.yaml delete mode 100644 zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-panic.yaml create mode 100644 zellij-utils/src/input/unit/fixtures/layouts/three-tabs-merged-correctly.yaml diff --git a/example/multiple_tabs_layout.yaml b/example/multiple_tabs_layout.yaml index 40689a96..6c4d1598 100644 --- a/example/multiple_tabs_layout.yaml +++ b/example/multiple_tabs_layout.yaml @@ -16,6 +16,7 @@ parts: - direction: Vertical split_size: Percent: 50 + - direction: Vertical - direction: Vertical parts: - direction: Vertical diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index 6a65109a..930fe07a 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 887350fb..c795cb55 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -20,6 +20,8 @@ use std::path::{Path, PathBuf}; use std::vec::Vec; use std::{fs::File, io::prelude::*}; +use super::config::{LayoutMissingTabSectionError, LayoutPartAndTabError}; + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(crate = "self::serde")] pub enum Direction { @@ -43,7 +45,7 @@ pub enum Run { Command(RunCommand), } -// The layout struct that is ultimately used to build the layouts +// The layout struct ultimately used to build the layouts. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(crate = "self::serde")] pub struct Layout { @@ -56,6 +58,7 @@ pub struct Layout { pub run: Option, } +// The tab-layout struct used to specify each individual tab. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(crate = "self::serde")] pub struct TabLayout { @@ -66,8 +69,8 @@ pub struct TabLayout { pub run: Option, } -// Main layout struct, that carries information based on -// position of tabs +// Main layout struct, that carries information based on position of tabs +// in relation to the whole layout. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(crate = "self::serde")] pub struct MainLayout { @@ -205,7 +208,9 @@ impl Layout { // Split the layout into parts that can be reassebled per tab // returns the layout pre tab, the parts post tab and the tab layouts - pub fn split_main_and_tab_layout(&self) -> (Layout, Vec, Vec) { + pub fn split_main_and_tab_layout( + &self, + ) -> Result<(Layout, Vec, Vec), LayoutPartAndTabError> { let mut main_layout = self.clone(); let mut pre_tab_layout = self.clone(); let mut post_tab_layout = vec![]; @@ -220,23 +225,21 @@ impl Layout { post_tab = true; } + if !main_layout.tabs.is_empty() && !main_layout.parts.is_empty() { + return Err(LayoutPartAndTabError); + } + for part in main_layout.parts.drain(..) { let (curr_pre_layout, mut curr_post_layout, mut curr_tabs) = - part.split_main_and_tab_layout(); + part.split_main_and_tab_layout()?; // Leaf if !post_tab && part.tabs.is_empty() { pre_tab_layout.parts.push(curr_pre_layout); } - // Todo: Convert into actual Error, or use the future logging system. if !part.tabs.is_empty() && !part.parts.is_empty() { - panic!("Tabs and Parts need to be specified separately."); - } - - // Todo: Convert into actual Error, or use the future logging system. - if (!part.tabs.is_empty() || !curr_tabs.is_empty()) && post_tab { - panic!("Only one tab section should be specified."); + return Err(LayoutPartAndTabError); } // Node @@ -259,7 +262,7 @@ impl Layout { } } } - (pre_tab_layout, post_tab_layout, tabs) + Ok((pre_tab_layout, post_tab_layout, tabs)) } pub fn merge_tab_layout(&mut self, tab: TabLayout) { @@ -271,33 +274,31 @@ impl Layout { } pub fn construct_full_layout(&self, tab_layout: Option) -> Self { + // The `split_main_and_tab_layout()` error should have returned + // already from deserialisation, so we can assume it is `Ok()`. + let (mut pre_tab_layout, post_tab_layout, _) = self.split_main_and_tab_layout().unwrap(); if let Some(tab_layout) = tab_layout { - let (mut pre_tab_layout, post_tab_layout, _) = self.split_main_and_tab_layout(); pre_tab_layout.merge_tab_layout(tab_layout); - pre_tab_layout.merge_layout_parts(post_tab_layout); - pre_tab_layout } else { - let (mut pre_tab_layout, post_tab_layout, _) = self.split_main_and_tab_layout(); let default_tab_layout = TabLayout::default(); pre_tab_layout.merge_tab_layout(default_tab_layout); - pre_tab_layout.merge_layout_parts(post_tab_layout); - pre_tab_layout } + pre_tab_layout.merge_layout_parts(post_tab_layout); + pre_tab_layout } - pub fn construct_main_layout(&self) -> MainLayout { - let (pre_tab, post_tab, tabs) = self.split_main_and_tab_layout(); + pub fn construct_main_layout(&self) -> Result { + let (pre_tab, post_tab, tabs) = self.split_main_and_tab_layout()?; - // Todo: A proper LayoutError if tabs.is_empty() { - panic!("The layout file should have a [`tabs`] section specified"); + return Err(ConfigError::Layout(LayoutMissingTabSectionError)); } - MainLayout { + Ok(MainLayout { pre_tab, post_tab, tabs, - } + }) } fn from_vec_tab_layout(tab_layout: Vec) -> Vec { diff --git a/zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-panic.yaml b/zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-not-error.yaml similarity index 100% rename from zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-panic.yaml rename to zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-not-error.yaml diff --git a/zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-panic.yaml b/zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-error.yaml similarity index 100% rename from zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-panic.yaml rename to zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-error.yaml diff --git a/zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-error.yaml b/zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-error.yaml new file mode 100644 index 00000000..e7223602 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-error.yaml @@ -0,0 +1,17 @@ +--- +direction: Horizontal +parts: + - direction: Horizontal + tabs: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 50 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 diff --git a/zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-panic.yaml b/zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-panic.yaml deleted file mode 100644 index 1ef5a05e..00000000 --- a/zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-panic.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- -direction: Horizontal -parts: - - direction: Horizontal - 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 - tabs: - - 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 00000000..96e1524d --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/layouts/three-tabs-merged-correctly.yaml @@ -0,0 +1,25 @@ +--- +direction: Vertical +parts: + - 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 + - direction: Horizontal diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index 5d9fda35..1a90c1ff 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -23,7 +23,7 @@ fn default_layout_is_ok() { fn default_layout_has_one_tab() { let path = default_layout_dir("default.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); assert_eq!(main_layout.tabs.len(), 1); } @@ -31,7 +31,7 @@ fn default_layout_has_one_tab() { fn default_layout_has_one_pre_tab() { let path = default_layout_dir("default.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); assert_eq!(main_layout.pre_tab.parts.len(), 1); } @@ -39,7 +39,7 @@ fn default_layout_has_one_pre_tab() { fn default_layout_has_one_post_tab() { let path = default_layout_dir("default.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); assert_eq!(main_layout.post_tab.len(), 1); } @@ -47,7 +47,7 @@ fn default_layout_has_one_post_tab() { fn default_layout_merged_correctly() { let path = default_layout_dir("default.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); let merged_layout = Layout { direction: Direction::Horizontal, @@ -85,7 +85,7 @@ fn default_layout_merged_correctly() { fn default_layout_new_tab_correct() { let path = default_layout_dir("default.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); let tab_layout = main_layout.construct_tab_layout(None); let merged_layout = Layout { direction: Direction::Horizontal, @@ -137,7 +137,7 @@ fn default_disable_status_layout_is_ok() { fn default_disable_status_layout_has_one_tab() { let path = default_layout_dir("disable-status-bar.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); assert_eq!(main_layout.tabs.len(), 1); } @@ -145,7 +145,7 @@ fn default_disable_status_layout_has_one_tab() { fn default_disable_status_layout_has_one_pre_tab() { let path = default_layout_dir("disable-status-bar.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); assert_eq!(main_layout.pre_tab.parts.len(), 1); } @@ -153,7 +153,7 @@ fn default_disable_status_layout_has_one_pre_tab() { fn default_disable_status_layout_has_no_post_tab() { let path = default_layout_dir("disable-status-bar.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); assert!(main_layout.post_tab.is_empty()); } @@ -168,7 +168,7 @@ fn three_panes_with_tab_is_ok() { fn three_panes_with_tab_has_one_tab() { let path = layout_test_dir("three-panes-with-tab.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); assert_eq!(main_layout.tabs.len(), 1); } @@ -176,7 +176,7 @@ fn three_panes_with_tab_has_one_tab() { fn three_panes_with_tab_no_post_tab() { let path = layout_test_dir("three-panes-with-tab.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); assert!(main_layout.post_tab.is_empty()); } @@ -184,7 +184,7 @@ fn three_panes_with_tab_no_post_tab() { fn three_panes_with_tab_no_pre_tab() { let path = layout_test_dir("three-panes-with-tab.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); assert!(main_layout.pre_tab.parts.is_empty()); } @@ -192,7 +192,7 @@ fn three_panes_with_tab_no_pre_tab() { fn three_panes_with_tab_merged_correctly() { let path = layout_test_dir("three-panes-with-tab.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); let merged_layout = Layout { direction: Direction::Horizontal, @@ -244,7 +244,7 @@ fn three_panes_with_tab_merged_correctly() { fn three_panes_with_tab_new_tab_is_correct() { let path = layout_test_dir("three-panes-with-tab.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); let tab_layout = main_layout.construct_tab_layout(None); let merged_layout = Layout { direction: Direction::Horizontal, @@ -273,7 +273,7 @@ fn three_panes_with_tab_and_default_plugins_is_ok() { 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 = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); assert_eq!(main_layout.tabs.len(), 1); } @@ -281,7 +281,7 @@ fn three_panes_with_tab_and_default_plugins_has_one_tab() { fn three_panes_with_tab_and_default_plugins_one_post_tab() { let path = layout_test_dir("three-panes-with-tab-and-default-plugins.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); assert_eq!(main_layout.post_tab.len(), 1); } @@ -289,7 +289,7 @@ fn three_panes_with_tab_and_default_plugins_one_post_tab() { fn three_panes_with_tab_and_default_plugins_has_pre_tab() { let path = layout_test_dir("three-panes-with-tab-and-default-plugins.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); assert!(!main_layout.pre_tab.parts.is_empty()); } @@ -297,7 +297,7 @@ fn three_panes_with_tab_and_default_plugins_has_pre_tab() { 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 = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); let merged_layout = Layout { direction: Direction::Horizontal, @@ -365,7 +365,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { 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 = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); let tab_layout = main_layout.construct_tab_layout(None); let merged_layout = Layout { direction: Direction::Horizontal, @@ -410,7 +410,7 @@ fn deeply_nested_tab_is_ok() { fn deeply_nested_tab_has_one_tab() { let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); assert_eq!(main_layout.tabs.len(), 1); } @@ -418,7 +418,7 @@ fn deeply_nested_tab_has_one_tab() { fn deeply_nested_tab_three_post_tab() { let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); assert_eq!(main_layout.post_tab.len(), 3); } @@ -426,7 +426,7 @@ fn deeply_nested_tab_three_post_tab() { fn deeply_nested_tab_has_many_pre_tab() { let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); assert!(!main_layout.pre_tab.parts.is_empty()); } @@ -434,7 +434,7 @@ fn deeply_nested_tab_has_many_pre_tab() { fn deeply_nested_tab_merged_correctly() { let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout(); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); let merged_layout = Layout { direction: Direction::Horizontal, @@ -519,17 +519,172 @@ fn deeply_nested_tab_merged_correctly() { } #[test] -#[should_panic] -// TODO Make error out of this -fn no_tabs_specified_should_panic() { - let path = layout_test_dir("no-tabs-should-panic.yaml".into()); +fn no_tabs_specified_should_err() { + let path = layout_test_dir("no-tabs-should-error.yaml".into()); let layout = Layout::new(&path); - let _main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout(); + assert!(main_layout.is_err()); } #[test] -fn multiple_tabs_specified_should_not_panic() { - let path = layout_test_dir("multiple-tabs-should-panic.yaml".into()); +fn tabs_and_parts_specified_together_should_should_err() { + let path = layout_test_dir("tabs-and-parts-together-should-error.yaml".into()); let layout = Layout::new(&path); - let _main_layout = layout.unwrap().construct_main_layout(); + let main_layout = layout.unwrap().construct_main_layout(); + assert!(main_layout.is_err()); +} + +#[test] +fn multiple_tabs_specified_should_not_err() { + let path = layout_test_dir("multiple-tabs-should-not-error.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout(); + assert!(main_layout.is_ok()) +} +#[test] +fn three_tabs_is_ok() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout = Layout::new(&path); + assert!(layout.is_ok()); +} + +#[test] +fn three_tabs_has_three_tabs() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); + assert_eq!(main_layout.tabs.len(), 3); +} + +#[test] +fn three_tabs_has_one_post_tab() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.unwrap().construct_main_layout().unwrap(); + assert_eq!(main_layout.post_tab.len(), 1); +} + +#[test] +fn three_tabs_tab_one_merged_correctly() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); + let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); + let merged_layout = Layout { + direction: Direction::Vertical, + parts: vec![ + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: None, + run: None, + }, + ], + tabs: vec![], + split_size: None, + run: None, + }; + + assert_eq!(merged_layout, tab_layout); +} + +#[test] +fn three_tabs_tab_two_merged_correctly() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); + let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[1].clone())); + let merged_layout = Layout { + direction: Direction::Vertical, + parts: vec![ + Layout { + direction: Direction::Horizontal, + parts: vec![ + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: None, + run: None, + }, + ], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: None, + run: None, + }, + ], + tabs: vec![], + split_size: None, + run: None, + }; + + assert_eq!(merged_layout, tab_layout); +} + +#[test] +fn three_tabs_tab_three_merged_correctly() { + let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); + let layout = Layout::new(&path); + let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); + let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[2].clone())); + let merged_layout = Layout { + direction: Direction::Vertical, + parts: vec![ + Layout { + direction: Direction::Vertical, + parts: vec![ + Layout { + direction: Direction::Vertical, + parts: vec![], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: None, + run: None, + }, + ], + tabs: vec![], + split_size: Some(SplitSize::Percent(50)), + run: None, + }, + Layout { + direction: Direction::Horizontal, + parts: vec![], + tabs: vec![], + split_size: None, + run: None, + }, + ], + tabs: vec![], + split_size: None, + run: None, + }; + assert_eq!(merged_layout, tab_layout); } diff --git a/zellij-utils/src/setup.rs b/zellij-utils/src/setup.rs index e00eb7ae..fe941ec8 100644 --- a/zellij-utils/src/setup.rs +++ b/zellij-utils/src/setup.rs @@ -159,13 +159,10 @@ impl Setup { _ => false, }; - log::info!("{:?}", clean); - let config = if !clean { match Config::try_from(opts) { Ok(config) => config, Err(e) => { - eprintln!("There was an error in the config file:"); return Err(e); } } @@ -188,12 +185,19 @@ 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.construct_main_layout()); + let layout = match layout { + None => None, + Some(Ok(layout)) => Some(layout), + Some(Err(e)) => { + return Err(e); + } + }; + if let Some(Command::Setup(ref setup)) = &opts.command { setup.from_cli(opts, &config_options).map_or_else( |e| { From 88b4063879845cf53397f60201473ce386280cfe Mon Sep 17 00:00:00 2001 From: a-kenji Date: Sat, 21 Aug 2021 23:27:23 +0200 Subject: [PATCH 6/6] Add `template` section in `layout` file It works as follows: ``` --- template: direction: Horizontal parts: - direction: Vertical split_size: Fixed: 1 run: plugin: tab-bar - direction: Vertical body: true - direction: Vertical split_size: Fixed: 2 run: plugin: status-bar tabs: - direction: Vertical ``` The tabs are created in the body section of the template. --- zellij-client/src/lib.rs | 4 +- zellij-server/src/lib.rs | 6 +- zellij-server/src/pty.rs | 4 +- zellij-utils/assets/layouts/default.yaml | 30 +- .../assets/layouts/disable-status-bar.yaml | 20 +- zellij-utils/assets/layouts/strider.yaml | 44 +-- zellij-utils/src/input/layout.rs | 272 ++++++++++++------ .../layouts/deeply-nested-tab-layout.yaml | 79 ++--- .../layouts/no-tabs-should-error.yaml | 18 -- .../tabs-and-parts-together-should-error.yaml | 17 -- .../three-panes-with-tab-and-command.yaml | 57 ++-- ...ee-panes-with-tab-and-default-plugins.yaml | 49 ++-- .../layouts/three-panes-with-tab.yaml | 31 +- .../layouts/three-tabs-merged-correctly.yaml | 36 +-- zellij-utils/src/input/unit/layout_test.rs | 225 +++++++-------- zellij-utils/src/ipc.rs | 4 +- zellij-utils/src/setup.rs | 16 +- 17 files changed, 484 insertions(+), 428 deletions(-) delete mode 100644 zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-error.yaml delete mode 100644 zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-error.yaml diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index ec0c3c74..f74a74dd 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -19,7 +19,7 @@ use zellij_utils::{ channels::{self, ChannelWithContext, SenderWithContext}, consts::{SESSION_NAME, ZELLIJ_IPC_PIPE}, errors::{ClientContext, ContextType, ErrorInstruction}, - input::{actions::Action, config::Config, layout::MainLayout, options::Options}, + input::{actions::Action, config::Config, layout::LayoutTemplate, options::Options}, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, }; @@ -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-server/src/lib.rs b/zellij-server/src/lib.rs index d90301cc..d0b3b5d2 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -36,7 +36,7 @@ use zellij_utils::{ input::{ command::{RunCommand, TerminalAction}, get_mode_info, - layout::MainLayout, + layout::LayoutTemplate, options::Options, }, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, @@ -50,7 +50,7 @@ pub(crate) enum ServerInstruction { ClientAttributes, Box, Box, - Option, + Option, ), Render(Option), UnblockInputThread, @@ -329,7 +329,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/pty.rs b/zellij-server/src/pty.rs index 5f01ca27..5c0e4a26 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, MainLayout, Run, TabLayout}, + layout::{Layout, LayoutTemplate, Run, TabLayout}, }, logging::debug_to_file, }; @@ -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())); diff --git a/zellij-utils/assets/layouts/default.yaml b/zellij-utils/assets/layouts/default.yaml index 39dbdc1b..c17cdd27 100644 --- a/zellij-utils/assets/layouts/default.yaml +++ b/zellij-utils/assets/layouts/default.yaml @@ -1,16 +1,18 @@ --- -direction: Horizontal -parts: +template: + direction: Horizontal + parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Vertical + body: true + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar +tabs: - direction: Vertical - split_size: - Fixed: 1 - run: - plugin: tab-bar - - direction: Vertical - tabs: - - 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 a0a59239..caa9f95b 100644 --- a/zellij-utils/assets/layouts/disable-status-bar.yaml +++ b/zellij-utils/assets/layouts/disable-status-bar.yaml @@ -1,11 +1,11 @@ --- -direction: Horizontal -parts: - - direction: Vertical - split_size: - Fixed: 1 - run: - plugin: tab-bar - - direction: Vertical - tabs: - - direction: Vertical +template: + direction: Horizontal + parts: + - direction: Vertical + 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 a39f327c..c12b0577 100644 --- a/zellij-utils/assets/layouts/strider.yaml +++ b/zellij-utils/assets/layouts/strider.yaml @@ -1,23 +1,25 @@ --- -direction: Horizontal -parts: +template: + direction: Horizontal + parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Vertical + body: true + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: status-bar +tabs: - direction: Vertical - split_size: - Fixed: 1 - run: - plugin: tab-bar - - direction: Vertical - tabs: - - direction: Vertical - parts: - - direction: Horizontal - split_size: - Percent: 20 - run: - plugin: strider - - direction: Horizontal - - direction: Vertical - split_size: - Fixed: 2 - run: - plugin: status-bar + parts: + - direction: Horizontal + split_size: + Percent: 20 + run: + plugin: strider + - direction: Horizontal diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index c795cb55..6baa5122 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -58,6 +58,159 @@ pub struct Layout { pub run: Option, } +// 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")] +pub struct LayoutFromYaml { + //#[serde(default)] + pub template: LayoutTemplateFromYaml, + #[serde(default)] + pub tabs: Vec, +} + +type LayoutFromYamlResult = Result; + +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: 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>) -> LayoutFromYamlResult { + match layout_dir { + Some(dir) => Self::new(&dir.join(layout)) + .or_else(|_| Self::from_default_assets(layout.as_path())), + None => Self::from_default_assets(layout.as_path()), + } + } + + pub fn from_path_or_default( + layout: Option<&PathBuf>, + layout_path: Option<&PathBuf>, + layout_dir: Option, + ) -> Option { + layout + .map(|p| LayoutFromYaml::from_dir(p, layout_dir.as_ref())) + .or_else(|| layout_path.map(|p| LayoutFromYaml::new(p))) + .or_else(|| { + Some(LayoutFromYaml::from_dir( + &std::path::PathBuf::from("default"), + layout_dir.as_ref(), + )) + }) + } + + pub fn construct_layout_template(&self) -> LayoutTemplate { + let (pre_tab, post_tab) = self.template.split_template().unwrap(); + LayoutTemplate { + pre_tab: pre_tab.into(), + post_tab: Layout::from_vec_template_layout(post_tab), + tabs: self.tabs.clone(), + } + } + + // Currently still needed but on nightly + // this is already possible: + // HashMap<&'static str, Vec> + pub fn from_default_assets(path: &Path) -> LayoutFromYamlResult { + match path.to_str() { + Some("default") => Self::default_from_assets(), + Some("strider") => Self::strider_from_assets(), + Some("disable-status-bar") => Self::disable_status_from_assets(), + None | Some(_) => Err(ConfigError::IoPath( + std::io::Error::new(std::io::ErrorKind::Other, "The layout was not found"), + path.into(), + )), + } + } + + // TODO Deserialize the assets from bytes &[u8], + // once serde-yaml supports zero-copy + 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() -> 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() -> 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 LayoutTemplateFromYaml { + pub direction: Direction, + #[serde(default)] + pub parts: Vec, + #[serde(default)] + pub body: bool, + pub split_size: Option, + pub run: Option, +} + +impl LayoutTemplateFromYaml { + // Split the layout into parts that can be reassebled per tab + // returns the layout pre tab and the parts post tab + pub fn split_template( + &self, + ) -> Result<(LayoutTemplateFromYaml, Vec), LayoutPartAndTabError> { + let mut main_layout = self.clone(); + let mut pre_tab_layout = self.clone(); + let mut post_tab_layout = vec![]; + let mut post_tab = false; + + pre_tab_layout.parts.clear(); + + if main_layout.body { + post_tab = true; + } + + for part in main_layout.parts.drain(..) { + let (curr_pre_layout, mut curr_post_layout) = part.split_template()?; + + // Leaf + if !post_tab && !part.body { + pre_tab_layout.parts.push(curr_pre_layout); + } + + // Node + if part.body { + post_tab = true; + // Leaf + } else if post_tab { + if curr_post_layout.is_empty() { + let mut part_no_tab = part.clone(); + part_no_tab.parts.clear(); + post_tab_layout.push(part_no_tab); + } else { + post_tab_layout.append(&mut curr_post_layout); + } + } + } + Ok((pre_tab_layout, post_tab_layout)) + } +} + // The tab-layout struct used to specify each individual tab. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(crate = "self::serde")] @@ -69,17 +222,17 @@ pub struct TabLayout { pub run: Option, } -// Main layout struct, that carries information based on position of tabs -// in relation to the whole layout. +// Main template layout struct, that carries information based on position of +// tabs in relation to the whole layout. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(crate = "self::serde")] -pub struct MainLayout { +pub struct LayoutTemplate { pub pre_tab: Layout, pub post_tab: Vec, pub tabs: Vec, } -impl MainLayout { +impl LayoutTemplate { pub fn construct_tab_layout(&self, tab_layout: Option) -> Layout { if let Some(tab_layout) = tab_layout { let mut pre_tab_layout = self.pre_tab.clone(); @@ -98,81 +251,7 @@ impl MainLayout { } } -type LayoutResult = Result; - impl Layout { - pub fn new(layout_path: &Path) -> LayoutResult { - 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)?; - 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 { - match layout_dir { - Some(dir) => Self::new(&dir.join(layout)) - .or_else(|_| Self::from_default_assets(layout.as_path())), - None => Self::from_default_assets(layout.as_path()), - } - } - - pub fn from_path_or_default( - layout: Option<&PathBuf>, - layout_path: Option<&PathBuf>, - layout_dir: Option, - ) -> Option> { - layout - .map(|p| Layout::from_dir(p, layout_dir.as_ref())) - .or_else(|| layout_path.map(|p| Layout::new(p))) - .or_else(|| { - Some(Layout::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 { - match path.to_str() { - Some("default") => Self::default_from_assets(), - Some("strider") => Self::strider_from_assets(), - Some("disable-status-bar") => Self::disable_status_from_assets(), - None | Some(_) => Err(ConfigError::IoPath( - std::io::Error::new(std::io::ErrorKind::Other, "The layout was not found"), - path.into(), - )), - } - } - - // TODO Deserialize the assets from bytes &[u8], - // once serde-yaml supports zero-copy - pub fn default_from_assets() -> LayoutResult { - let layout: Layout = - 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 = - 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 = - serde_yaml::from_str(String::from_utf8(setup::NO_STATUS_LAYOUT.to_vec())?.as_str())?; - Ok(layout) - } - pub fn total_terminal_panes(&self) -> usize { let mut total_panes = 0; total_panes += self.parts.len(); @@ -273,28 +352,14 @@ impl Layout { self.parts.append(&mut parts); } - pub fn construct_full_layout(&self, tab_layout: Option) -> Self { - // The `split_main_and_tab_layout()` error should have returned - // already from deserialisation, so we can assume it is `Ok()`. - let (mut pre_tab_layout, post_tab_layout, _) = self.split_main_and_tab_layout().unwrap(); - if let Some(tab_layout) = tab_layout { - pre_tab_layout.merge_tab_layout(tab_layout); - } else { - let default_tab_layout = TabLayout::default(); - pre_tab_layout.merge_tab_layout(default_tab_layout); - } - pre_tab_layout.merge_layout_parts(post_tab_layout); - pre_tab_layout - } - - pub fn construct_main_layout(&self) -> Result { + pub fn construct_layout_template(&self) -> Result { let (pre_tab, post_tab, tabs) = self.split_main_and_tab_layout()?; if tabs.is_empty() { return Err(ConfigError::Layout(LayoutMissingTabSectionError)); } - Ok(MainLayout { + Ok(LayoutTemplate { pre_tab, post_tab, tabs, @@ -307,6 +372,13 @@ impl Layout { .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( @@ -474,6 +546,18 @@ impl From for Layout { } } +impl From for Layout { + fn from(template: LayoutTemplateFromYaml) -> Self { + Layout { + direction: template.direction, + parts: Layout::from_vec_template_layout(template.parts), + tabs: vec![], + split_size: template.split_size, + run: template.run, + } + } +} + impl Default for TabLayout { fn default() -> Self { Self { 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 index 11e5323e..633a1fae 100644 --- 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 @@ -1,40 +1,43 @@ --- -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 - split_size: - Percent: 77 - tabs: - - direction: Horizontal - split_size: - Percent: 24 +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 + split_size: + Percent: 77 + 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: 90 - - direction: Vertical - split_size: - Percent: 15 - - direction: Vertical - split_size: - Percent: 15 - - direction: Vertical - split_size: - Percent: 15 + Percent: 24 diff --git a/zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-error.yaml b/zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-error.yaml deleted file mode 100644 index a67b0ff9..00000000 --- a/zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-error.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -direction: Horizontal -parts: - - direction: Horizontal - parts: - - 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/tabs-and-parts-together-should-error.yaml b/zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-error.yaml deleted file mode 100644 index e7223602..00000000 --- a/zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-error.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -direction: Horizontal -parts: - - direction: Horizontal - tabs: - - direction: Vertical - parts: - - direction: Horizontal - split_size: - Percent: 50 - 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-and-command.yaml b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-command.yaml index 34de4291..9ad1034d 100644 --- 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 @@ -1,32 +1,35 @@ --- -direction: Horizontal -parts: +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 - split_size: - Fixed: 1 - run: - plugin: tab-bar - - direction: Horizontal - tabs: - - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 50 + - direction: Horizontal parts: - - direction: Horizontal + - direction: Vertical 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"]} - - direction: Vertical - split_size: - Fixed: 2 - run: - plugin: status-bar + 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 index a803f291..88046395 100644 --- 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 @@ -1,28 +1,31 @@ --- -direction: Horizontal -parts: +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 - split_size: - Fixed: 1 - run: - plugin: tab-bar - - direction: Horizontal - tabs: - - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 50 + - direction: Horizontal parts: - - direction: Horizontal + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical split_size: Percent: 50 - - direction: Horizontal - parts: - - direction: Vertical - split_size: - Percent: 50 - - direction: Vertical - split_size: - Percent: 50 - - direction: Vertical - split_size: - Fixed: 2 - run: - plugin: status-bar 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 index e0f25b92..83f05076 100644 --- 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 @@ -1,18 +1,21 @@ --- -direction: Horizontal -parts: - - direction: Horizontal - tabs: - - direction: Vertical +template: + direction: Horizontal + parts: + - direction: Horizontal + body: true + +tabs: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 50 + - direction: Horizontal parts: - - direction: Horizontal + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical 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 index 96e1524d..e2a09a73 100644 --- 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 @@ -1,25 +1,29 @@ --- -direction: Vertical -parts: +template: + direction: Vertical + parts: + - direction: Horizontal + body: true + - direction: Horizontal + + +tabs: - direction: Horizontal - tabs: + split_size: + Percent: 50 + - direction: Horizontal + split_size: + Percent: 50 + parts: - 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 - parts: - - direction: Vertical - split_size: - Percent: 50 - - direction: Horizontal - - direction: Horizontal + - direction: Horizontal diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index 1a90c1ff..d09d328c 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -15,40 +15,43 @@ fn default_layout_dir(layout: String) -> PathBuf { #[test] fn default_layout_is_ok() { let path = default_layout_dir("default.yaml".into()); - let layout = Layout::new(&path); + 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 = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.tabs.len(), 1); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap().construct_layout_template(); + assert_eq!(layout_template.tabs.len(), 1); } #[test] fn default_layout_has_one_pre_tab() { let path = default_layout_dir("default.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.pre_tab.parts.len(), 1); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap().construct_layout_template(); + assert_eq!(layout_template.pre_tab.parts.len(), 1); } #[test] fn default_layout_has_one_post_tab() { let path = default_layout_dir("default.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.post_tab.len(), 1); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap().construct_layout_template(); + assert_eq!(layout_template.post_tab.len(), 1); } #[test] fn default_layout_merged_correctly() { let path = default_layout_dir("default.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml + .as_ref() + .unwrap() + .construct_layout_template(); + let tab_layout = layout_template.construct_tab_layout(Some(layout_template.tabs[0].clone())); let merged_layout = Layout { direction: Direction::Horizontal, parts: vec![ @@ -84,9 +87,12 @@ fn default_layout_merged_correctly() { #[test] fn default_layout_new_tab_correct() { let path = default_layout_dir("default.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - let tab_layout = main_layout.construct_tab_layout(None); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml + .as_ref() + .unwrap() + .construct_layout_template(); + let tab_layout = layout_template.construct_tab_layout(None); let merged_layout = Layout { direction: Direction::Horizontal, parts: vec![ @@ -122,78 +128,84 @@ fn default_layout_new_tab_correct() { #[test] fn default_strider_layout_is_ok() { let path = default_layout_dir("strider.yaml".into()); - let layout = Layout::new(&path); - assert!(layout.is_ok()); + 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 = Layout::new(&path); - assert!(layout.is_ok()); + let layout_from_yaml = LayoutFromYaml::new(&path); + assert!(layout_from_yaml.is_ok()); } #[test] -fn default_disable_status_layout_has_one_tab() { +fn default_disable_status_layout_has_no_tab() { let path = default_layout_dir("disable-status-bar.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.tabs.len(), 1); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml + .as_ref() + .unwrap() + .construct_layout_template(); + assert_eq!(layout_template.tabs.len(), 0); } #[test] fn default_disable_status_layout_has_one_pre_tab() { let path = default_layout_dir("disable-status-bar.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.pre_tab.parts.len(), 1); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml + .as_ref() + .unwrap() + .construct_layout_template(); + assert_eq!(layout_template.pre_tab.parts.len(), 1); } #[test] fn default_disable_status_layout_has_no_post_tab() { let path = default_layout_dir("disable-status-bar.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - assert!(main_layout.post_tab.is_empty()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap().construct_layout_template(); + assert!(layout_template.post_tab.is_empty()); } #[test] fn three_panes_with_tab_is_ok() { let path = layout_test_dir("three-panes-with-tab.yaml".into()); - let layout = Layout::new(&path); + 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 = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.tabs.len(), 1); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap().construct_layout_template(); + assert_eq!(layout_template.tabs.len(), 1); } #[test] fn three_panes_with_tab_no_post_tab() { let path = layout_test_dir("three-panes-with-tab.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert!(main_layout.post_tab.is_empty()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap().construct_layout_template(); + assert!(layout_template.post_tab.is_empty()); } #[test] fn three_panes_with_tab_no_pre_tab() { let path = layout_test_dir("three-panes-with-tab.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert!(main_layout.pre_tab.parts.is_empty()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap().construct_layout_template(); + assert!(layout_template.pre_tab.parts.is_empty()); } #[test] fn three_panes_with_tab_merged_correctly() { let path = layout_test_dir("three-panes-with-tab.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap().construct_layout_template(); + let tab_layout = layout_template.construct_tab_layout(Some(layout_template.tabs[0].clone())); let merged_layout = Layout { direction: Direction::Horizontal, parts: vec![Layout { @@ -243,9 +255,9 @@ fn three_panes_with_tab_merged_correctly() { #[test] fn three_panes_with_tab_new_tab_is_correct() { let path = layout_test_dir("three-panes-with-tab.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - let tab_layout = main_layout.construct_tab_layout(None); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap().construct_layout_template(); + let tab_layout = layout_template.construct_tab_layout(None); let merged_layout = Layout { direction: Direction::Horizontal, parts: vec![Layout { @@ -265,40 +277,40 @@ fn three_panes_with_tab_new_tab_is_correct() { #[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 = Layout::new(&path); + 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 = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.tabs.len(), 1); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap().construct_layout_template(); + assert_eq!(layout_template.tabs.len(), 1); } #[test] fn three_panes_with_tab_and_default_plugins_one_post_tab() { let path = layout_test_dir("three-panes-with-tab-and-default-plugins.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.post_tab.len(), 1); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap().construct_layout_template(); + assert_eq!(layout_template.post_tab.len(), 1); } #[test] fn three_panes_with_tab_and_default_plugins_has_pre_tab() { let path = layout_test_dir("three-panes-with-tab-and-default-plugins.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert!(!main_layout.pre_tab.parts.is_empty()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap().construct_layout_template(); + assert!(!layout_template.pre_tab.parts.is_empty()); } #[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 = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap().construct_layout_template(); + let tab_layout = layout_template.construct_tab_layout(Some(layout_template.tabs[0].clone())); let merged_layout = Layout { direction: Direction::Horizontal, parts: vec![ @@ -364,9 +376,9 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { #[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 = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - let tab_layout = main_layout.construct_tab_layout(None); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap().construct_layout_template(); + let tab_layout = layout_template.construct_tab_layout(None); let merged_layout = Layout { direction: Direction::Horizontal, parts: vec![ @@ -402,40 +414,40 @@ fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() { #[test] fn deeply_nested_tab_is_ok() { let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); - let layout = Layout::new(&path); + 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 = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.tabs.len(), 1); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap().construct_layout_template(); + assert_eq!(layout_template.tabs.len(), 1); } #[test] fn deeply_nested_tab_three_post_tab() { let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.post_tab.len(), 3); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap().construct_layout_template(); + assert_eq!(layout_template.post_tab.len(), 3); } #[test] fn deeply_nested_tab_has_many_pre_tab() { let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert!(!main_layout.pre_tab.parts.is_empty()); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.unwrap().construct_layout_template(); + assert!(!layout_template.pre_tab.parts.is_empty()); } #[test] fn deeply_nested_tab_merged_correctly() { let path = layout_test_dir("deeply-nested-tab-layout.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap().construct_layout_template(); + let tab_layout = layout_template.construct_tab_layout(Some(layout_template.tabs[0].clone())); let merged_layout = Layout { direction: Direction::Horizontal, parts: vec![ @@ -518,58 +530,38 @@ fn deeply_nested_tab_merged_correctly() { assert_eq!(merged_layout, tab_layout); } -#[test] -fn no_tabs_specified_should_err() { - let path = layout_test_dir("no-tabs-should-error.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); - assert!(main_layout.is_err()); -} - -#[test] -fn tabs_and_parts_specified_together_should_should_err() { - let path = layout_test_dir("tabs-and-parts-together-should-error.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); - assert!(main_layout.is_err()); -} - -#[test] -fn multiple_tabs_specified_should_not_err() { - let path = layout_test_dir("multiple-tabs-should-not-error.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout(); - assert!(main_layout.is_ok()) -} #[test] fn three_tabs_is_ok() { let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); - let layout = Layout::new(&path); - assert!(layout.is_ok()); + 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 = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.tabs.len(), 3); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.unwrap().construct_layout_template(); + assert_eq!(layout_template.tabs.len(), 3); } #[test] fn three_tabs_has_one_post_tab() { let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.unwrap().construct_main_layout().unwrap(); - assert_eq!(main_layout.post_tab.len(), 1); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml.unwrap().construct_layout_template(); + assert_eq!(layout_template.post_tab.len(), 1); } #[test] fn three_tabs_tab_one_merged_correctly() { let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[0].clone())); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml + .as_ref() + .unwrap() + .construct_layout_template(); + let tab_layout = layout_template.construct_tab_layout(Some(layout_template.tabs[0].clone())); let merged_layout = Layout { direction: Direction::Vertical, parts: vec![ @@ -599,9 +591,12 @@ fn three_tabs_tab_one_merged_correctly() { #[test] fn three_tabs_tab_two_merged_correctly() { let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[1].clone())); + let layout_from_yaml = LayoutFromYaml::new(&path); + let layout_template = layout_from_yaml + .as_ref() + .unwrap() + .construct_layout_template(); + let tab_layout = layout_template.construct_tab_layout(Some(layout_template.tabs[1].clone())); let merged_layout = Layout { direction: Direction::Vertical, parts: vec![ @@ -646,9 +641,9 @@ fn three_tabs_tab_two_merged_correctly() { #[test] fn three_tabs_tab_three_merged_correctly() { let path = layout_test_dir("three-tabs-merged-correctly.yaml".into()); - let layout = Layout::new(&path); - let main_layout = layout.as_ref().unwrap().construct_main_layout().unwrap(); - let tab_layout = main_layout.construct_tab_layout(Some(main_layout.tabs[2].clone())); + let layout = LayoutFromYaml::new(&path); + let layout_template = layout.as_ref().unwrap().construct_layout_template(); + let tab_layout = layout_template.construct_tab_layout(Some(layout_template.tabs[2].clone())); let merged_layout = Layout { direction: Direction::Vertical, parts: vec![ diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index 18910901..d0799d4b 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -3,7 +3,7 @@ use crate::{ cli::CliArgs, errors::{get_current_ctx, ErrorContext}, - input::{actions::Action, layout::MainLayout, options::Options}, + input::{actions::Action, layout::LayoutTemplate, options::Options}, pane_size::PositionAndSize, }; use interprocess::local_socket::LocalSocketStream; @@ -62,7 +62,7 @@ pub enum ClientToServerMsg { ClientAttributes, Box, Box, - Option, + Option, ), AttachClient(ClientAttributes, bool, Options), Action(Action), diff --git a/zellij-utils/src/setup.rs b/zellij-utils/src/setup.rs index fe941ec8..c680822d 100644 --- a/zellij-utils/src/setup.rs +++ b/zellij-utils/src/setup.rs @@ -6,7 +6,7 @@ use crate::{ }, input::{ config::{Config, ConfigError}, - layout::{Layout, MainLayout}, + layout::{LayoutFromYaml, LayoutTemplate}, options::Options, }, }; @@ -153,7 +153,7 @@ impl Setup { /// 2. config options (`config.yaml`) pub fn from_options( opts: &CliArgs, - ) -> Result<(Config, Option, Options), ConfigError> { + ) -> Result<(Config, Option, Options), ConfigError> { let clean = match &opts.command { Some(Command::Setup(ref setup)) => setup.clean, _ => false, @@ -176,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 = Layout::from_path_or_default( + let layout_result = LayoutFromYaml::from_path_or_default( opts.layout.as_ref(), opts.layout_path.as_ref(), layout_dir, @@ -188,15 +188,7 @@ impl Setup { return Err(e); } } - .map(|layout| layout.construct_main_layout()); - - let layout = match layout { - None => None, - Some(Ok(layout)) => Some(layout), - Some(Err(e)) => { - return Err(e); - } - }; + .map(|layout| layout.construct_layout_template()); if let Some(Command::Setup(ref setup)) = &opts.command { setup.from_cli(opts, &config_options).map_or_else(