summaryrefslogtreecommitdiffstats
path: root/zellij-utils
diff options
context:
space:
mode:
authora-kenji <aks.kenji@protonmail.com>2021-07-09 23:37:36 +0200
committera-kenji <aks.kenji@protonmail.com>2021-07-23 17:13:35 +0200
commit5ede25dc37ceb192032524ac9200bb1ca95e5863 (patch)
tree1b2a19f3110ca12ec8705dcb0c07502746c06ff5 /zellij-utils
parent3df0210647e65170576f1ea97328cce26c211a43 (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')
-rw-r--r--zellij-utils/assets/config/default.yaml2
-rw-r--r--zellij-utils/assets/layouts/default.yaml2
-rw-r--r--zellij-utils/assets/layouts/disable-status-bar.yaml2
-rw-r--r--zellij-utils/assets/layouts/strider.yaml16
-rw-r--r--zellij-utils/src/input/actions.rs7
-rw-r--r--zellij-utils/src/input/layout.rs186
-rw-r--r--zellij-utils/src/input/unit/fixtures/layouts/deeply-nested-tab-layout.yaml40
-rw-r--r--zellij-utils/src/input/unit/fixtures/layouts/multiple-tabs-should-panic.yaml18
-rw-r--r--zellij-utils/src/input/unit/fixtures/layouts/no-tabs-should-panic.yaml18
-rw-r--r--zellij-utils/src/input/unit/fixtures/layouts/tabs-and-parts-together-should-panic.yaml25
-rw-r--r--zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-command.yaml32
-rw-r--r--zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml28
-rw-r--r--zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab.yaml18
-rw-r--r--zellij-utils/src/input/unit/keybinds_test.rs6
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs538
-rw-r--r--zellij-utils/src/ipc.rs23
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)
+}
+