summaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/input/layout.rs
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2021-08-24 10:58:36 +0200
committerAram Drevekenin <aram@poor.dev>2021-08-24 10:58:36 +0200
commit618c2c376bb4edca8693ea4b871279bc04501559 (patch)
treeb4d4426b353955e8af1ed8ad6531f95860eda5f6 /zellij-utils/src/input/layout.rs
parent7a2f86db1b34b027ca03be73ec7bc4681f33fbdf (diff)
parent88b4063879845cf53397f60201473ce386280cfe (diff)
Merge branch 'tab-layout' of https://github.com/a-kenji/zellij into a-kenji-tab-layout
Diffstat (limited to 'zellij-utils/src/input/layout.rs')
-rw-r--r--zellij-utils/src/input/layout.rs300
1 files changed, 280 insertions, 20 deletions
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index 33e08bfdb..96f6189c9 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -17,23 +17,26 @@ 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)]
+use super::config::{LayoutMissingTabSectionError, LayoutPartAndTabError};
+
+#[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,35 +45,49 @@ pub enum Run {
Command(RunCommand),
}
-#[derive(Debug, Serialize, Deserialize, Clone)]
+// The layout struct ultimately used to build the layouts.
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(crate = "self::serde")]
pub struct Layout {
pub direction: Direction,
#[serde(default)]
pub parts: Vec<Layout>,
+ #[serde(default)]
+ pub tabs: Vec<TabLayout>,
pub split_size: Option<SplitSize>,
pub run: Option<Run>,
#[serde(default)]
pub borderless: bool,
}
-type LayoutResult = Result<Layout, ConfigError>;
+// 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<TabLayout>,
+}
-impl Layout {
- pub fn new(layout_path: &Path) -> LayoutResult {
+type LayoutFromYamlResult = Result<LayoutFromYaml, ConfigError>;
+
+impl LayoutFromYaml {
+ pub fn new(layout_path: &Path) -> LayoutFromYamlResult {
let mut layout_file = File::open(&layout_path)
.or_else(|_| File::open(&layout_path.with_extension("yaml")))
.map_err(|e| ConfigError::IoPath(e, layout_path.into()))?;
let mut layout = String::new();
layout_file.read_to_string(&mut layout)?;
- let layout: Layout = serde_yaml::from_str(&layout)?;
+ let layout: LayoutFromYaml = serde_yaml::from_str(&layout)?;
Ok(layout)
}
// It wants to use Path here, but that doesn't compile.
#[allow(clippy::ptr_arg)]
- pub fn from_dir(layout: &PathBuf, layout_dir: Option<&PathBuf>) -> LayoutResult {
+ pub fn from_dir(layout: &PathBuf, layout_dir: Option<&PathBuf>) -> LayoutFromYamlResult {
match layout_dir {
Some(dir) => Self::new(&dir.join(layout))
.or_else(|_| Self::from_default_assets(layout.as_path())),
@@ -82,22 +99,31 @@ impl Layout {
layout: Option<&PathBuf>,
layout_path: Option<&PathBuf>,
layout_dir: Option<PathBuf>,
- ) -> Option<Result<Layout, ConfigError>> {
+ ) -> Option<LayoutFromYamlResult> {
layout
- .map(|p| Layout::from_dir(p, layout_dir.as_ref()))
- .or_else(|| layout_path.map(|p| Layout::new(p)))
+ .map(|p| LayoutFromYaml::from_dir(p, layout_dir.as_ref()))
+ .or_else(|| layout_path.map(|p| LayoutFromYaml::new(p)))
.or_else(|| {
- Some(Layout::from_dir(
+ Some(LayoutFromYaml::from_dir(
&std::path::PathBuf::from("default"),
layout_dir.as_ref(),
))
})
}
+ 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<u8>>
- pub fn from_default_assets(path: &Path) -> LayoutResult {
+ pub fn from_default_assets(path: &Path) -> LayoutFromYamlResult {
match path.to_str() {
Some("default") => Self::default_from_assets(),
Some("strider") => Self::strider_from_assets(),
@@ -111,24 +137,123 @@ impl Layout {
// TODO Deserialize the assets from bytes &[u8],
// once serde-yaml supports zero-copy
- pub fn default_from_assets() -> LayoutResult {
- let layout: Layout =
+ pub fn default_from_assets() -> LayoutFromYamlResult {
+ let layout: LayoutFromYaml =
serde_yaml::from_str(String::from_utf8(setup::DEFAULT_LAYOUT.to_vec())?.as_str())?;
Ok(layout)
}
- pub fn strider_from_assets() -> LayoutResult {
- let layout: Layout =
+ pub fn strider_from_assets() -> LayoutFromYamlResult {
+ let layout: LayoutFromYaml =
serde_yaml::from_str(String::from_utf8(setup::STRIDER_LAYOUT.to_vec())?.as_str())?;
Ok(layout)
}
- pub fn disable_status_from_assets() -> LayoutResult {
- let layout: Layout =
+ pub fn disable_status_from_assets() -> LayoutFromYamlResult {
+ let layout: LayoutFromYaml =
serde_yaml::from_str(String::from_utf8(setup::NO_STATUS_LAYOUT.to_vec())?.as_str())?;
Ok(layout)
}
+}
+
+// The struct that carries the information template that is used to
+// construct the layout
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[serde(crate = "self::serde")]
+pub struct LayoutTemplateFromYaml {
+ pub direction: Direction,
+ #[serde(default)]
+ pub parts: Vec<LayoutTemplateFromYaml>,
+ #[serde(default)]
+ pub body: bool,
+ pub split_size: Option<SplitSize>,
+ pub run: Option<Run>,
+}
+
+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<LayoutTemplateFromYaml>), 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")]
+pub struct TabLayout {
+ pub direction: Direction,
+ #[serde(default)]
+ pub parts: Vec<TabLayout>,
+ pub split_size: Option<SplitSize>,
+ pub run: Option<Run>,
+}
+
+// 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 LayoutTemplate {
+ pub pre_tab: Layout,
+ pub post_tab: Vec<Layout>,
+ pub tabs: Vec<TabLayout>,
+}
+
+impl LayoutTemplate {
+ 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
+ }
+ }
+}
+impl Layout {
pub fn total_terminal_panes(&self) -> usize {
let mut total_panes = 0;
total_panes += self.parts.len();
@@ -169,6 +294,101 @@ 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,
+ ) -> Result<(Layout, Vec<Layout>, Vec<TabLayout>), LayoutPartAndTabError> {
+ 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;
+ }
+
+ 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()?;
+
+ // Leaf
+ if !post_tab && part.tabs.is_empty() {
+ pre_tab_layout.parts.push(curr_pre_layout);
+ }
+
+ if !part.tabs.is_empty() && !part.parts.is_empty() {
+ return Err(LayoutPartAndTabError);
+ }
+
+ // 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);
+ }
+ }
+ }
+ Ok((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_layout_template(&self) -> Result<LayoutTemplate, ConfigError> {
+ let (pre_tab, post_tab, tabs) = self.split_main_and_tab_layout()?;
+
+ if tabs.is_empty() {
+ return Err(ConfigError::Layout(LayoutMissingTabSectionError));
+ }
+
+ Ok(LayoutTemplate {
+ 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 from_vec_template_layout(layout_template: Vec<LayoutTemplateFromYaml>) -> Vec<Self> {
+ layout_template
+ .iter()
+ .map(|layout_template| Layout::from(layout_template.to_owned()))
+ .collect()
+ }
}
fn split_space_to_parts_vertically(
@@ -323,3 +543,43 @@ 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 From<LayoutTemplateFromYaml> 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 {
+ direction: Direction::Horizontal,
+ parts: vec![],
+ split_size: None,
+ run: None,
+ }
+ }
+}
+
+// The unit test location.
+#[cfg(test)]
+#[path = "./unit/layout_test.rs"]
+mod layout_test;