diff options
author | a-kenji <aks.kenji@protonmail.com> | 2021-08-21 23:27:23 +0200 |
---|---|---|
committer | a-kenji <aks.kenji@protonmail.com> | 2021-08-21 23:27:23 +0200 |
commit | 88b4063879845cf53397f60201473ce386280cfe (patch) | |
tree | 2451496e64350111d503673bcdac11a4f2b5d6a6 | |
parent | 2e1775678577e8587ca0830a15810212c4b209f7 (diff) |
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.
17 files changed, 456 insertions, 400 deletions
diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index ec0c3c745..f74a74ddf 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<MainLayout>, + layout: Option<LayoutTemplate>, ) { 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 d90301cc6..d0b3b5d27 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<CliArgs>, Box<Options>, - Option<MainLayout>, + Option<LayoutTemplate>, ), Render(Option<String>), UnblockInputThread, @@ -329,7 +329,7 @@ fn init_session( to_server: SenderWithContext<ServerInstruction>, client_attributes: ClientAttributes, session_state: Arc<RwLock<SessionState>>, - layout: Option<MainLayout>, + layout: Option<LayoutTemplate>, ) -> SessionMetaData { let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded(); let to_screen = SenderWithContext::new(to_screen); diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 5f01ca27d..5c0e4a269 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<RawFd, JoinHandle<()>>, } -pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option<MainLayout>) { +pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option<LayoutTemplate>) { 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 39dbdc1b0..c17cdd273 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 a0a592390..caa9f95ba 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 a39f327cf..c12b05779 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 c795cb55b..6baa5122c 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -58,63 +58,34 @@ pub struct Layout { pub run: Option<Run>, } -// The tab-layout struct used to specify each individual tab. +// 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 TabLayout { - pub direction: Direction, +pub struct LayoutFromYaml { + //#[serde(default)] + pub template: LayoutTemplateFromYaml, #[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 -// in relation to the whole layout. -#[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>; +type LayoutFromYamlResult = Result<LayoutFromYaml, ConfigError>; -impl Layout { - pub fn new(layout_path: &Path) -> LayoutResult { +impl LayoutFromYaml { + pub fn new(layout_path: &Path) -> LayoutFromYamlResult { let mut layout_file = File::open(&layout_path) .or_else(|_| File::open(&layout_path.with_extension("yaml"))) .map_err(|e| ConfigError::IoPath(e, layout_path.into()))?; let mut layout = String::new(); layout_file.read_to_string(&mut layout)?; - let layout: Layout = serde_yaml::from_str(&layout)?; + let layout: LayoutFromYaml = serde_yaml::from_str(&layout)?; Ok(layout) } // It wants to use Path here, but that doesn't compile. #[allow(clippy::ptr_arg)] - pub fn from_dir(layout: &PathBuf, layout_dir: Option<&PathBuf>) -> LayoutResult { + pub fn from_dir(layout: &PathBuf, layout_dir: Option<&PathBuf>) -> LayoutFromYamlResult { match layout_dir { Some(dir) => Self::new(&dir.join(layout)) .or_else(|_| Self::from_default_assets(layout.as_path())), @@ -126,22 +97,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(), @@ -155,24 +135,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(); @@ -273,28 +352,14 @@ impl Layout { self.parts.append(&mut parts); } - pub fn construct_full_layout(&self, tab_layout: Option<TabLayout>) -> 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<MainLayout, ConfigError> { + 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(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<LayoutTemplateFromYaml>) -> Vec<Self> { + 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<TabLayout> for Layout { } } +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 { 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 11e5323e1..633a1fae2 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 a67b0ff95..000000000 --- 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 |