diff options
author | a-kenji <aks.kenji@protonmail.com> | 2021-07-09 23:37:36 +0200 |
---|---|---|
committer | a-kenji <aks.kenji@protonmail.com> | 2021-07-23 17:13:35 +0200 |
commit | 5ede25dc37ceb192032524ac9200bb1ca95e5863 (patch) | |
tree | 1b2a19f3110ca12ec8705dcb0c07502746c06ff5 /zellij-utils | |
parent | 3df0210647e65170576f1ea97328cce26c211a43 (diff) |
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,]
```
Diffstat (limited to 'zellij-utils')
16 files changed, 935 insertions, 26 deletions
diff --git a/zellij-utils/assets/config/default.yaml b/zellij-utils/assets/config/default.yaml index d9090a8dc..e90b60a18 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 96bf1809c..39dbdc1b0 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 b990ba500..a0a592390 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 9bbe5772f..a39f327cf 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 bfd7d2559..ab711c75c 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<Direction>), /// Close the focus pane. CloseFocus, - /// Create a new tab. - NewTab, + /// Create a new tab, optionally with a specified tab layout. + NewTab(Option<TabLayout>), /// 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 a48c35219..713605e9c 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<Layout>, + #[serde(default)] + pub tabs: Vec<TabLayout>, + pub split_size: Option<SplitSize>, + pub run: Option<Run>, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(crate = "self::serde")] +pub struct TabLayout { + pub direction: Direction, + #[serde(default)] + pub parts: Vec<TabLayout>, pub split_size: Option<SplitSize>, pub run: Option<Run>, } +// 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<Layout>, + pub tabs: Vec<TabLayout>, +} + +impl MainLayout { + pub fn construct_tab_layout(&self, tab_layout: Option<TabLayout>) -> 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<Layout, ConfigError>; 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<Layout>, Vec<TabLayout>) { + 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<Layout>) { + self.parts.append(&mut parts); + } + + pub fn construct_full_layout(&self, tab_layout: Option<TabLayout>) -> 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<TabLayout>) -> Vec<Self> { + 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<TabLayout> 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 000000000..11e5323e1 --- /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 000000000..653787591 --- /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 000000000..a67b0ff95 --- /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 000000000..1ef5a05ee --- /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 000000000..34de4291a --- /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 000000000..a803f2916 --- /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 000000000..e0f25b922 --- /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 4767fc157..e8a776d76 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 000000000..95de356cc --- /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) +} + |