Merge pull request #625 from a-kenji/tab-layout

Add `tabs` to `layouts`
This commit is contained in:
a-kenji 2021-08-26 17:47:23 +02:00 committed by GitHub
commit f802663067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1580 additions and 182 deletions

View File

@ -0,0 +1,87 @@
---
template:
direction: Horizontal
parts:
- direction: Vertical
split_size:
Fixed: 1
run:
plugin: tab-bar
borderless: true
- direction: Vertical
body: true
- direction: Vertical
split_size:
Fixed: 2
run:
plugin: status-bar
borderless: true
tabs:
- direction: Vertical
parts:
- direction: Vertical
split_size:
Percent: 50
- direction: Vertical
split_size:
Percent: 50
- direction: Vertical
- 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

View File

@ -0,0 +1,90 @@
---
template:
direction: Horizontal
parts:
- direction: Vertical
split_size:
Fixed: 1
run:
plugin: tab-bar
borderless: true
- direction: Vertical
body: true
- direction: Vertical
split_size:
Fixed: 2
run:
plugin: status-bar
borderless: true
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

View File

@ -0,0 +1,21 @@
---
tabs:
- direction: Vertical
parts:
- direction: Horizontal
split_size:
Percent: 50
parts:
- direction: Vertical
split_size:
Percent: 50
- direction: Vertical
split_size:
Percent: 50
run:
command: {cmd: htop}
- direction: Horizontal
split_size:
Percent: 50
run:
command: {cmd: htop}

View File

@ -0,0 +1,33 @@
---
tabs:
- direction: Horizontal
parts:
- direction: Vertical
split_size:
Fixed: 1
run:
plugin: tab-bar
borderless: true
- direction: Vertical
parts:
- direction: Vertical
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: 5
- direction: Vertical
split_size:
Fixed: 2
run:
plugin: status-bar
borderless: true

View File

@ -1,20 +0,0 @@
---
direction: Vertical
parts:
- direction: Horizontal
split_size:
Percent: 50
parts:
- direction: Vertical
split_size:
Percent: 50
- direction: Vertical
split_size:
Percent: 50
run:
command: {cmd: htop}
- direction: Horizontal
split_size:
Percent: 50
run:
command: {cmd: htop}

View File

@ -1,30 +0,0 @@
---
direction: Horizontal
parts:
- direction: Vertical
split_size:
Fixed: 1
run:
plugin: tab-bar
- direction: Vertical
parts:
- direction: Vertical
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: 5
- direction: Vertical
split_size:
Fixed: 2
run:
plugin: status-bar

View File

@ -24,14 +24,6 @@ pub fn main() {
list_sessions(); list_sessions();
} }
let (config, layout, config_options) = match Setup::from_options(&opts) {
Ok(results) => results,
Err(e) => {
eprintln!("{}", e);
process::exit(1);
}
};
atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap(); atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap();
atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap(); atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap();
if let Some(path) = opts.server { if let Some(path) = opts.server {
@ -62,6 +54,14 @@ pub fn main() {
session_name = Some(get_active_session()); session_name = Some(get_active_session());
} }
let (config, _, config_options) = match Setup::from_options(&opts) {
Ok(results) => results,
Err(e) => {
eprintln!("{}", e);
process::exit(1);
}
};
start_client( start_client(
Box::new(os_input), Box::new(os_input),
opts, opts,
@ -70,6 +70,14 @@ pub fn main() {
None, None,
); );
} else { } else {
let (config, layout, _) = match Setup::from_options(&opts) {
Ok(results) => results,
Err(e) => {
eprintln!("{}", e);
process::exit(1);
}
};
let session_name = opts let session_name = opts
.session .session
.clone() .clone()

View File

@ -1,5 +1,6 @@
--- ---
direction: Horizontal tabs:
- direction: Horizontal
parts: parts:
- direction: Vertical - direction: Vertical
parts: parts:
@ -9,6 +10,8 @@
- direction: Horizontal - direction: Horizontal
split_size: split_size:
Percent: 50 Percent: 50
tabs:
- direction: Horizontal
split_size: split_size:
Percent: 80 Percent: 80
- direction: Vertical - direction: Vertical

View File

@ -1,5 +1,6 @@
--- ---
direction: Horizontal tabs:
- direction: Horizontal
parts: parts:
- direction: Vertical - direction: Vertical
parts: parts:
@ -9,6 +10,9 @@
- direction: Horizontal - direction: Horizontal
split_size: split_size:
Percent: 90 Percent: 90
- direction: Horizontal
tabs:
- direction: Horizontal
split_size: split_size:
Percent: 80 Percent: 80
- direction: Vertical - direction: Vertical

View File

@ -1,5 +1,6 @@
--- ---
direction: Horizontal tabs:
- direction: Horizontal
parts: parts:
- direction: Vertical - direction: Vertical
parts: parts:

View File

@ -185,7 +185,7 @@ impl InputHandler {
} }
Action::CloseFocus Action::CloseFocus
| Action::NewPane(_) | Action::NewPane(_)
| Action::NewTab | Action::NewTab(_)
| Action::GoToNextTab | Action::GoToNextTab
| Action::GoToPreviousTab | Action::GoToPreviousTab
| Action::CloseTab | Action::CloseTab

View File

@ -14,14 +14,14 @@ use crate::{
command_is_executing::CommandIsExecuting, input_handler::input_loop, command_is_executing::CommandIsExecuting, input_handler::input_loop,
os_input_output::ClientOsApi, os_input_output::ClientOsApi,
}; };
use zellij_utils::cli::CliArgs;
use zellij_utils::{ use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext}, channels::{self, ChannelWithContext, SenderWithContext},
consts::{SESSION_NAME, ZELLIJ_IPC_PIPE}, consts::{SESSION_NAME, ZELLIJ_IPC_PIPE},
errors::{ClientContext, ContextType, ErrorInstruction}, errors::{ClientContext, ContextType, ErrorInstruction},
input::{actions::Action, config::Config, layout::Layout, options::Options}, input::{actions::Action, config::Config, options::Options},
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
}; };
use zellij_utils::{cli::CliArgs, input::layout::LayoutFromYaml};
/// Instructions related to the client-side application /// Instructions related to the client-side application
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -87,7 +87,7 @@ pub fn start_client(
opts: CliArgs, opts: CliArgs,
config: Config, config: Config,
info: ClientInfo, info: ClientInfo,
layout: Option<Layout>, layout: Option<LayoutFromYaml>,
) { ) {
info!("Starting Zellij client!"); 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"; 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";

View File

@ -11,11 +11,13 @@ mod ui;
mod wasm_vm; mod wasm_vm;
use log::info; use log::info;
use std::{
path::PathBuf,
sync::{Arc, Mutex, RwLock},
thread,
};
use zellij_utils::zellij_tile; use zellij_utils::zellij_tile;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use wasmer::Store; use wasmer::Store;
use zellij_tile::data::{Event, Palette, PluginCapabilities}; use zellij_tile::data::{Event, Palette, PluginCapabilities};
@ -34,7 +36,7 @@ use zellij_utils::{
input::{ input::{
command::{RunCommand, TerminalAction}, command::{RunCommand, TerminalAction},
get_mode_info, get_mode_info,
layout::Layout, layout::LayoutFromYaml,
options::Options, options::Options,
}, },
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
@ -44,7 +46,12 @@ use zellij_utils::{
/// Instructions related to server-side application /// Instructions related to server-side application
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) enum ServerInstruction { pub(crate) enum ServerInstruction {
NewClient(ClientAttributes, Box<CliArgs>, Box<Options>, Option<Layout>), NewClient(
ClientAttributes,
Box<CliArgs>,
Box<Options>,
Option<LayoutFromYaml>,
),
Render(Option<String>), Render(Option<String>),
UnblockInputThread, UnblockInputThread,
ClientExit, ClientExit,
@ -207,7 +214,7 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
to_server.clone(), to_server.clone(),
client_attributes, client_attributes,
session_state.clone(), session_state.clone(),
layout, layout.clone(),
); );
*session_data.write().unwrap() = Some(session); *session_data.write().unwrap() = Some(session);
*session_state.write().unwrap() = SessionState::Attached; *session_state.write().unwrap() = SessionState::Attached;
@ -219,14 +226,31 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
}) })
}); });
let spawn_tabs = |tab_layout| {
session_data session_data
.read() .read()
.unwrap() .unwrap()
.as_ref() .as_ref()
.unwrap() .unwrap()
.senders .senders
.send_to_pty(PtyInstruction::NewTab(default_shell.clone())) .send_to_pty(PtyInstruction::NewTab(default_shell.clone(), tab_layout))
.unwrap(); .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()));
}
} else {
spawn_tabs(None);
}
}
}
} }
ServerInstruction::AttachClient(attrs, _, options) => { ServerInstruction::AttachClient(attrs, _, options) => {
*session_state.write().unwrap() = SessionState::Attached; *session_state.write().unwrap() = SessionState::Attached;
@ -302,7 +326,7 @@ fn init_session(
to_server: SenderWithContext<ServerInstruction>, to_server: SenderWithContext<ServerInstruction>,
client_attributes: ClientAttributes, client_attributes: ClientAttributes,
session_state: Arc<RwLock<SessionState>>, session_state: Arc<RwLock<SessionState>>,
layout: Option<Layout>, layout: Option<LayoutFromYaml>,
) -> SessionMetaData { ) -> SessionMetaData {
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded(); let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded();
let to_screen = SenderWithContext::new(to_screen); let to_screen = SenderWithContext::new(to_screen);

View File

@ -20,7 +20,7 @@ use zellij_utils::{
errors::{get_current_ctx, ContextType, PtyContext}, errors::{get_current_ctx, ContextType, PtyContext},
input::{ input::{
command::TerminalAction, command::TerminalAction,
layout::{Layout, Run}, layout::{Layout, LayoutFromYaml, Run, TabLayout},
}, },
logging::debug_to_file, logging::debug_to_file,
}; };
@ -33,7 +33,7 @@ pub(crate) enum PtyInstruction {
SpawnTerminal(Option<TerminalAction>), SpawnTerminal(Option<TerminalAction>),
SpawnTerminalVertically(Option<TerminalAction>), SpawnTerminalVertically(Option<TerminalAction>),
SpawnTerminalHorizontally(Option<TerminalAction>), SpawnTerminalHorizontally(Option<TerminalAction>),
NewTab(Option<TerminalAction>), NewTab(Option<TerminalAction>, Option<TabLayout>),
ClosePane(PaneId), ClosePane(PaneId),
CloseTab(Vec<PaneId>), CloseTab(Vec<PaneId>),
Exit, Exit,
@ -47,7 +47,7 @@ impl From<&PtyInstruction> for PtyContext {
PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally, PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally,
PtyInstruction::ClosePane(_) => PtyContext::ClosePane, PtyInstruction::ClosePane(_) => PtyContext::ClosePane,
PtyInstruction::CloseTab(_) => PtyContext::CloseTab, PtyInstruction::CloseTab(_) => PtyContext::CloseTab,
PtyInstruction::NewTab(_) => PtyContext::NewTab, PtyInstruction::NewTab(..) => PtyContext::NewTab,
PtyInstruction::Exit => PtyContext::Exit, PtyInstruction::Exit => PtyContext::Exit,
} }
} }
@ -60,7 +60,7 @@ pub(crate) struct Pty {
task_handles: HashMap<RawFd, JoinHandle<()>>, task_handles: HashMap<RawFd, JoinHandle<()>>,
} }
pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option<Layout>) { pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option<LayoutFromYaml>) {
loop { loop {
let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel"); let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Pty((&event).into())); err_ctx.add_call(ContextType::Pty((&event).into()));
@ -86,11 +86,12 @@ pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option<Layout>) {
.send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) .send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
.unwrap(); .unwrap();
} }
PtyInstruction::NewTab(terminal_action) => { PtyInstruction::NewTab(terminal_action, tab_layout) => {
if let Some(layout) = maybe_layout.clone() { if let Some(layout) = maybe_layout.clone() {
pty.spawn_terminals_for_layout(layout, terminal_action); let merged_layout = layout.template.insert_tab_layout(tab_layout);
pty.spawn_terminals_for_layout(merged_layout.into(), terminal_action.clone());
} else { } else {
let pid = pty.spawn_terminal(terminal_action); let pid = pty.spawn_terminal(terminal_action.clone());
pty.bus pty.bus
.senders .senders
.send_to_screen(ScreenInstruction::NewTab(pid)) .send_to_screen(ScreenInstruction::NewTab(pid))

View File

@ -197,11 +197,11 @@ fn route_action(
.send_to_screen(ScreenInstruction::CloseFocusedPane) .send_to_screen(ScreenInstruction::CloseFocusedPane)
.unwrap(); .unwrap();
} }
Action::NewTab => { Action::NewTab(tab_layout) => {
let shell = session.default_shell.clone(); let shell = session.default_shell.clone();
session session
.senders .senders
.send_to_pty(PtyInstruction::NewTab(shell)) .send_to_pty(PtyInstruction::NewTab(shell, tab_layout))
.unwrap(); .unwrap();
} }
Action::GoToNextTab => { Action::GoToNextTab => {

View File

@ -136,7 +136,7 @@ keybinds:
key: [ Char: 'h', Left, Up, Char: 'k',] key: [ Char: 'h', Left, Up, Char: 'k',]
- action: [GoToNextTab,] - action: [GoToNextTab,]
key: [ Char: 'l', Right,Down, Char: 'j'] key: [ Char: 'l', Right,Down, Char: 'j']
- action: [NewTab,] - action: [NewTab: ,]
key: [ Char: 'n',] key: [ Char: 'n',]
- action: [CloseTab,] - action: [CloseTab,]
key: [ Char: 'x',] key: [ Char: 'x',]

View File

@ -1,4 +1,5 @@
--- ---
template:
direction: Horizontal direction: Horizontal
parts: parts:
- direction: Vertical - direction: Vertical
@ -8,9 +9,12 @@ parts:
run: run:
plugin: tab-bar plugin: tab-bar
- direction: Vertical - direction: Vertical
body: true
- direction: Vertical - direction: Vertical
borderless: true borderless: true
split_size: split_size:
Fixed: 2 Fixed: 2
run: run:
plugin: status-bar plugin: status-bar
tabs:
- direction: Vertical

View File

@ -1,4 +1,5 @@
--- ---
template:
direction: Horizontal direction: Horizontal
parts: parts:
- direction: Vertical - direction: Vertical
@ -8,3 +9,4 @@ parts:
run: run:
plugin: tab-bar plugin: tab-bar
- direction: Vertical - direction: Vertical
body: true

View File

@ -1,4 +1,5 @@
--- ---
template:
direction: Horizontal direction: Horizontal
parts: parts:
- direction: Vertical - direction: Vertical
@ -7,6 +8,15 @@ parts:
Fixed: 1 Fixed: 1
run: run:
plugin: tab-bar plugin: tab-bar
- direction: Vertical
body: true
- direction: Vertical
borderless: true
split_size:
Fixed: 2
run:
plugin: status-bar
tabs:
- direction: Vertical - direction: Vertical
parts: parts:
- direction: Horizontal - direction: Horizontal
@ -15,9 +25,3 @@ parts:
run: run:
plugin: strider plugin: strider
- direction: Horizontal - direction: Horizontal
- direction: Vertical
borderless: true
split_size:
Fixed: 2
run:
plugin: status-bar

View File

@ -1,6 +1,7 @@
//! Definition of the actions that can be bound to keys. //! Definition of the actions that can be bound to keys.
use super::command::RunCommandAction; use super::command::RunCommandAction;
use super::layout::TabLayout;
use crate::input::options::OnForceClose; use crate::input::options::OnForceClose;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zellij_tile::data::InputMode; use zellij_tile::data::InputMode;
@ -19,7 +20,7 @@ pub enum Direction {
// As these actions are bound to the default config, please // As these actions are bound to the default config, please
// do take care when refactoring - or renaming. // do take care when refactoring - or renaming.
// They might need to be adjusted in the default config // 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. /// Actions that can be bound to keys.
#[derive(Eq, Clone, Debug, PartialEq, Deserialize, Serialize)] #[derive(Eq, Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Action { pub enum Action {
@ -65,8 +66,8 @@ pub enum Action {
NewPane(Option<Direction>), NewPane(Option<Direction>),
/// Close the focus pane. /// Close the focus pane.
CloseFocus, CloseFocus,
/// Create a new tab. /// Create a new tab, optionally with a specified tab layout.
NewTab, NewTab(Option<TabLayout>),
/// Do nothing. /// Do nothing.
NoOp, NoOp,
/// Go to the next tab. /// Go to the next tab.

View File

@ -45,6 +45,9 @@ pub enum ConfigError {
IoPath(io::Error, PathBuf), IoPath(io::Error, PathBuf),
// Internal Deserialization Error // Internal Deserialization Error
FromUtf8(std::string::FromUtf8Error), FromUtf8(std::string::FromUtf8Error),
// Missing the tab section in the layout.
Layout(LayoutMissingTabSectionError),
LayoutPartAndTab(LayoutPartAndTabError),
} }
impl Default for Config { 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 { impl Display for ConfigError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
@ -138,6 +210,12 @@ impl Display for ConfigError {
} }
ConfigError::Serde(ref err) => write!(formatter, "Deserialization error: {}", err), ConfigError::Serde(ref err) => write!(formatter, "Deserialization error: {}", err),
ConfigError::FromUtf8(ref err) => write!(formatter, "FromUtf8Error: {}", 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::IoPath(ref err, _) => Some(err),
ConfigError::Serde(ref err) => Some(err), ConfigError::Serde(ref err) => Some(err),
ConfigError::FromUtf8(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<std::string::FromUtf8Error> for ConfigError {
} }
} }
impl From<LayoutMissingTabSectionError> for ConfigError {
fn from(err: LayoutMissingTabSectionError) -> ConfigError {
ConfigError::Layout(err)
}
}
impl From<LayoutPartAndTabError> for ConfigError {
fn from(err: LayoutPartAndTabError) -> ConfigError {
ConfigError::LayoutPartAndTab(err)
}
}
// The unit test location. // The unit test location.
#[cfg(test)] #[cfg(test)]
mod config_test { mod config_test {

View File

@ -17,23 +17,24 @@ use crate::{serde, serde_yaml};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::vec::Vec;
use std::{fs::File, io::prelude::*}; use std::{fs::File, io::prelude::*};
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(crate = "self::serde")] #[serde(crate = "self::serde")]
pub enum Direction { pub enum Direction {
Horizontal, Horizontal,
Vertical, Vertical,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Copy)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(crate = "self::serde")] #[serde(crate = "self::serde")]
pub enum SplitSize { pub enum SplitSize {
Percent(u8), // 1 to 100 Percent(u8), // 1 to 100
Fixed(u16), // An absolute number of columns or rows 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")] #[serde(crate = "self::serde")]
pub enum Run { pub enum Run {
#[serde(rename = "plugin")] #[serde(rename = "plugin")]
@ -42,7 +43,8 @@ pub enum Run {
Command(RunCommand), 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")] #[serde(crate = "self::serde")]
pub struct Layout { pub struct Layout {
pub direction: Direction, pub direction: Direction,
@ -54,23 +56,38 @@ pub struct Layout {
pub borderless: bool, 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")]
#[serde(default)]
pub struct LayoutFromYaml {
#[serde(default)]
pub template: LayoutTemplate,
#[serde(default)]
pub borderless: bool,
#[serde(default)]
pub tabs: Vec<TabLayout>,
}
impl Layout { type LayoutFromYamlResult = Result<LayoutFromYaml, ConfigError>;
pub fn new(layout_path: &Path) -> LayoutResult {
impl LayoutFromYaml {
pub fn new(layout_path: &Path) -> LayoutFromYamlResult {
let mut layout_file = File::open(&layout_path) let mut layout_file = File::open(&layout_path)
.or_else(|_| File::open(&layout_path.with_extension("yaml"))) .or_else(|_| File::open(&layout_path.with_extension("yaml")))
.map_err(|e| ConfigError::IoPath(e, layout_path.into()))?; .map_err(|e| ConfigError::IoPath(e, layout_path.into()))?;
let mut layout = String::new(); let mut layout = String::new();
layout_file.read_to_string(&mut layout)?; 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) Ok(layout)
} }
// It wants to use Path here, but that doesn't compile. // It wants to use Path here, but that doesn't compile.
#[allow(clippy::ptr_arg)] #[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 { match layout_dir {
Some(dir) => Self::new(&dir.join(layout)) Some(dir) => Self::new(&dir.join(layout))
.or_else(|_| Self::from_default_assets(layout.as_path())), .or_else(|_| Self::from_default_assets(layout.as_path())),
@ -82,22 +99,21 @@ impl Layout {
layout: Option<&PathBuf>, layout: Option<&PathBuf>,
layout_path: Option<&PathBuf>, layout_path: Option<&PathBuf>,
layout_dir: Option<PathBuf>, layout_dir: Option<PathBuf>,
) -> Option<Result<Layout, ConfigError>> { ) -> Option<LayoutFromYamlResult> {
layout layout
.map(|p| Layout::from_dir(p, layout_dir.as_ref())) .map(|p| LayoutFromYaml::from_dir(p, layout_dir.as_ref()))
.or_else(|| layout_path.map(|p| Layout::new(p))) .or_else(|| layout_path.map(|p| LayoutFromYaml::new(p)))
.or_else(|| { .or_else(|| {
Some(Layout::from_dir( Some(LayoutFromYaml::from_dir(
&std::path::PathBuf::from("default"), &std::path::PathBuf::from("default"),
layout_dir.as_ref(), layout_dir.as_ref(),
)) ))
}) })
} }
// Currently still needed but on nightly // Currently still needed but on nightly
// this is already possible: // this is already possible:
// HashMap<&'static str, Vec<u8>> // 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() { match path.to_str() {
Some("default") => Self::default_from_assets(), Some("default") => Self::default_from_assets(),
Some("strider") => Self::strider_from_assets(), Some("strider") => Self::strider_from_assets(),
@ -111,24 +127,83 @@ impl Layout {
// TODO Deserialize the assets from bytes &[u8], // TODO Deserialize the assets from bytes &[u8],
// once serde-yaml supports zero-copy // once serde-yaml supports zero-copy
pub fn default_from_assets() -> LayoutResult { pub fn default_from_assets() -> LayoutFromYamlResult {
let layout: Layout = let layout: LayoutFromYaml =
serde_yaml::from_str(String::from_utf8(setup::DEFAULT_LAYOUT.to_vec())?.as_str())?; serde_yaml::from_str(String::from_utf8(setup::DEFAULT_LAYOUT.to_vec())?.as_str())?;
Ok(layout) Ok(layout)
} }
pub fn strider_from_assets() -> LayoutResult { pub fn strider_from_assets() -> LayoutFromYamlResult {
let layout: Layout = let layout: LayoutFromYaml =
serde_yaml::from_str(String::from_utf8(setup::STRIDER_LAYOUT.to_vec())?.as_str())?; serde_yaml::from_str(String::from_utf8(setup::STRIDER_LAYOUT.to_vec())?.as_str())?;
Ok(layout) Ok(layout)
} }
pub fn disable_status_from_assets() -> LayoutResult { pub fn disable_status_from_assets() -> LayoutFromYamlResult {
let layout: Layout = let layout: LayoutFromYaml =
serde_yaml::from_str(String::from_utf8(setup::NO_STATUS_LAYOUT.to_vec())?.as_str())?; serde_yaml::from_str(String::from_utf8(setup::NO_STATUS_LAYOUT.to_vec())?.as_str())?;
Ok(layout) 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 LayoutTemplate {
pub direction: Direction,
#[serde(default)]
pub borderless: bool,
#[serde(default)]
pub parts: Vec<LayoutTemplate>,
#[serde(default)]
pub body: bool,
pub split_size: Option<SplitSize>,
pub run: Option<Run>,
}
impl LayoutTemplate {
// Insert an optional `[TabLayout]` at the correct postion
pub fn insert_tab_layout(mut self, tab_layout: Option<TabLayout>) -> Self {
if self.body {
return tab_layout.unwrap_or_default().into();
}
for (i, part) in self.parts.clone().iter().enumerate() {
if part.body {
self.parts.push(tab_layout.unwrap_or_default().into());
self.parts.swap_remove(i);
break;
}
// recurse
let new_part = part.clone().insert_tab_layout(tab_layout.clone());
self.parts.push(new_part);
self.parts.swap_remove(i);
}
self
}
fn from_vec_tab_layout(tab_layout: Vec<TabLayout>) -> Vec<Self> {
tab_layout
.iter()
.map(|tab_layout| Self::from(tab_layout.to_owned()))
.collect()
}
}
// 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 borderless: bool,
#[serde(default)]
pub parts: Vec<TabLayout>,
pub split_size: Option<SplitSize>,
pub run: Option<Run>,
}
impl Layout {
pub fn total_terminal_panes(&self) -> usize { pub fn total_terminal_panes(&self) -> usize {
let mut total_panes = 0; let mut total_panes = 0;
total_panes += self.parts.len(); total_panes += self.parts.len();
@ -169,6 +244,28 @@ impl Layout {
) -> Vec<(Layout, PositionAndSize)> { ) -> Vec<(Layout, PositionAndSize)> {
split_space(space, self) split_space(space, self)
} }
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);
}
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<LayoutTemplate>) -> Vec<Self> {
layout_template
.iter()
.map(|layout_template| Layout::from(layout_template.to_owned()))
.collect()
}
} }
fn split_space_to_parts_vertically( fn split_space_to_parts_vertically(
@ -323,3 +420,87 @@ fn split_space(
} }
pane_positions pane_positions
} }
impl From<TabLayout> for Layout {
fn from(tab: TabLayout) -> Self {
Layout {
direction: tab.direction,
borderless: tab.borderless,
parts: Self::from_vec_tab_layout(tab.parts),
split_size: tab.split_size,
run: tab.run,
}
}
}
impl From<TabLayout> for LayoutTemplate {
fn from(tab: TabLayout) -> Self {
Self {
direction: tab.direction,
borderless: tab.borderless,
parts: Self::from_vec_tab_layout(tab.parts),
body: false,
split_size: tab.split_size,
run: tab.run,
}
}
}
impl From<LayoutTemplate> for Layout {
fn from(template: LayoutTemplate) -> Self {
Layout {
direction: template.direction,
borderless: template.borderless,
parts: Self::from_vec_template_layout(template.parts),
split_size: template.split_size,
run: template.run,
}
}
}
impl Default for TabLayout {
fn default() -> Self {
Self {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: None,
run: None,
}
}
}
impl Default for LayoutTemplate {
fn default() -> Self {
Self {
direction: Direction::Horizontal,
body: false,
borderless: false,
parts: vec![LayoutTemplate {
direction: Direction::Horizontal,
body: true,
borderless: false,
split_size: None,
run: None,
parts: vec![],
}],
split_size: None,
run: None,
}
}
}
impl Default for LayoutFromYaml {
fn default() -> Self {
Self {
template: LayoutTemplate::default(),
borderless: false,
tabs: vec![],
}
}
}
// The unit test location.
#[cfg(test)]
#[path = "./unit/layout_test.rs"]
mod layout_test;

View File

@ -0,0 +1,41 @@
---
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
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: 24

View File

@ -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

View File

@ -0,0 +1,6 @@
---
tabs:
- direction: Vertical
parts:
- direction: Horizontal
- direction: Horizontal

View File

@ -0,0 +1,6 @@
---
template:
direction: Horizontal
parts:
- direction: Horizontal
body: true

View File

@ -0,0 +1,35 @@
---
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
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"]}

View File

@ -0,0 +1,31 @@
---
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
parts:
- direction: Horizontal
split_size:
Percent: 50
- direction: Horizontal
parts:
- direction: Vertical
split_size:
Percent: 50
- direction: Vertical
split_size:
Percent: 50

View File

@ -0,0 +1,21 @@
---
template:
direction: Horizontal
parts:
- direction: Horizontal
body: true
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

View File

@ -0,0 +1,29 @@
---
template:
direction: Vertical
parts:
- direction: Horizontal
body: true
- 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

View File

@ -150,7 +150,7 @@ fn no_unbind_unbinds_none() {
#[test] #[test]
fn last_keybind_is_taken() { 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 { let keyaction_1 = KeyActionFromYaml {
action: actions_1.clone(), action: actions_1.clone(),
key: vec![Key::F(1), Key::Backspace, Key::Char('t')], key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
@ -171,7 +171,7 @@ fn last_keybind_is_taken() {
#[test] #[test]
fn last_keybind_overwrites() { 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 { let keyaction_1 = KeyActionFromYaml {
action: actions_1.clone(), action: actions_1.clone(),
key: vec![Key::F(1), Key::Backspace, Key::Char('t')], 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() { fn uppercase_and_lowercase_are_distinct() {
let key_action_n = KeyActionFromYaml { let key_action_n = KeyActionFromYaml {
key: vec![Key::Char('n')], key: vec![Key::Char('n')],
action: vec![Action::NewTab], action: vec![Action::NewTab(None)],
}; };
let key_action_large_n = KeyActionFromYaml { let key_action_large_n = KeyActionFromYaml {
key: vec![Key::Char('N')], key: vec![Key::Char('N')],

View File

@ -0,0 +1,696 @@
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 = LayoutFromYaml::new(&path);
assert!(layout.is_ok());
}
#[test]
fn default_layout_has_one_tab() {
let path = default_layout_dir("default.yaml".into());
let layout = LayoutFromYaml::new(&path);
let layout_template = layout.as_ref().unwrap();
assert_eq!(layout_template.tabs.len(), 1);
}
#[test]
fn default_layout_merged_correctly() {
let path = default_layout_dir("default.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
let layout_template = layout_from_yaml.as_ref().unwrap();
let tab_layout = layout_template
.template
.clone()
.insert_tab_layout(Some(layout_template.tabs[0].clone()));
let merged_layout = Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![
Layout {
direction: Direction::Vertical,
borderless: true,
parts: vec![],
split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(Some("tab-bar".into()))),
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: None,
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: true,
parts: vec![],
split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(Some("status-bar".into()))),
},
],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[test]
fn default_layout_new_tab_correct() {
let path = default_layout_dir("default.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
let layout_template = layout_from_yaml.as_ref().unwrap();
let tab_layout = layout_template.template.clone().insert_tab_layout(None);
let merged_layout = Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![
Layout {
direction: Direction::Vertical,
borderless: true,
parts: vec![],
split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(Some("tab-bar".into()))),
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: None,
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: true,
parts: vec![],
split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(Some("status-bar".into()))),
},
],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[test]
fn default_strider_layout_is_ok() {
let path = default_layout_dir("strider.yaml".into());
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_from_yaml = LayoutFromYaml::new(&path);
assert!(layout_from_yaml.is_ok());
}
#[test]
fn default_disable_status_layout_has_no_tab() {
let path = default_layout_dir("disable-status-bar.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
let layout_template = layout_from_yaml.as_ref().unwrap();
assert_eq!(layout_template.tabs.len(), 0);
}
#[test]
fn three_panes_with_tab_is_ok() {
let path = layout_test_dir("three-panes-with-tab.yaml".into());
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 = LayoutFromYaml::new(&path);
let layout_template = layout.unwrap();
assert_eq!(layout_template.tabs.len(), 1);
}
#[test]
fn three_panes_with_tab_merged_correctly() {
let path = layout_test_dir("three-panes-with-tab.yaml".into());
let layout = LayoutFromYaml::new(&path);
let layout_template = layout.as_ref().unwrap();
let tab_layout = layout_template
.template
.clone()
.insert_tab_layout(Some(layout_template.tabs[0].clone()));
let merged_layout = Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
],
split_size: None,
run: None,
},
],
split_size: None,
run: None,
}],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[test]
fn three_panes_with_tab_new_tab_is_correct() {
let path = layout_test_dir("three-panes-with-tab.yaml".into());
let layout = LayoutFromYaml::new(&path);
let layout_template = layout.as_ref().unwrap();
let tab_layout = layout_template.template.clone().insert_tab_layout(None);
let merged_layout = Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: None,
run: None,
}],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[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 = 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 = LayoutFromYaml::new(&path);
let layout_template = layout.unwrap();
assert_eq!(layout_template.tabs.len(), 1);
}
#[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 = LayoutFromYaml::new(&path);
let layout_template = layout.as_ref().unwrap();
let tab_layout = layout_template
.template
.clone()
.insert_tab_layout(Some(layout_template.tabs[0].clone()));
let merged_layout = Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(Some("tab-bar".into()))),
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
],
split_size: None,
run: None,
},
],
split_size: None,
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(Some("status-bar".into()))),
},
],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[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 = LayoutFromYaml::new(&path);
let layout_template = layout.as_ref().unwrap();
let tab_layout = layout_template.template.clone().insert_tab_layout(None);
let merged_layout = Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(Some("tab-bar".into()))),
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: None,
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(Some("status-bar".into()))),
},
],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[test]
fn deeply_nested_tab_is_ok() {
let path = layout_test_dir("deeply-nested-tab-layout.yaml".into());
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 = LayoutFromYaml::new(&path);
let layout_template = layout.unwrap();
assert_eq!(layout_template.tabs.len(), 1);
}
#[test]
fn deeply_nested_tab_merged_correctly() {
let path = layout_test_dir("deeply-nested-tab-layout.yaml".into());
let layout = LayoutFromYaml::new(&path);
let layout_template = layout.as_ref().unwrap();
let tab_layout = layout_template
.template
.clone()
.insert_tab_layout(Some(layout_template.tabs[0].clone()));
let merged_layout = Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(21)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(22)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(23)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(24)),
run: None,
},
],
split_size: Some(SplitSize::Percent(78)),
run: None,
},
],
split_size: Some(SplitSize::Percent(79)),
run: None,
},
],
split_size: Some(SplitSize::Percent(90)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(15)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(15)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(15)),
run: None,
},
],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[test]
fn three_tabs_is_ok() {
let path = layout_test_dir("three-tabs-merged-correctly.yaml".into());
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_from_yaml = LayoutFromYaml::new(&path);
let layout_template = layout_from_yaml.unwrap();
assert_eq!(layout_template.tabs.len(), 3);
}
#[test]
fn three_tabs_tab_one_merged_correctly() {
let path = layout_test_dir("three-tabs-merged-correctly.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
let layout_template = layout_from_yaml.as_ref().unwrap();
let tab_layout = layout_template
.template
.clone()
.insert_tab_layout(Some(layout_template.tabs[0].clone()));
let merged_layout = Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: None,
run: None,
},
],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[test]
fn three_tabs_tab_two_merged_correctly() {
let path = layout_test_dir("three-tabs-merged-correctly.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
let layout_template = layout_from_yaml.as_ref().unwrap();
let tab_layout = layout_template
.template
.clone()
.insert_tab_layout(Some(layout_template.tabs[1].clone()));
let merged_layout = Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: None,
run: None,
},
],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: None,
run: None,
},
],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[test]
fn three_tabs_tab_three_merged_correctly() {
let path = layout_test_dir("three-tabs-merged-correctly.yaml".into());
let layout = LayoutFromYaml::new(&path);
let layout_template = layout.as_ref().unwrap();
let tab_layout = layout_template
.template
.clone()
.insert_tab_layout(Some(layout_template.tabs[2].clone()));
let merged_layout = Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: None,
run: None,
},
],
split_size: Some(SplitSize::Percent(50)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: None,
run: None,
},
],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[test]
fn no_tabs_is_ok() {
let path = layout_test_dir("no-tab-section-specified.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
assert!(layout_from_yaml.is_ok());
}
#[test]
fn no_tabs_has_no_tabs() {
let path = layout_test_dir("no-tab-section-specified.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
let layout_template = layout_from_yaml.unwrap();
assert_eq!(layout_template.tabs.len(), 0);
}
#[test]
fn no_tabs_merged_correctly() {
let path = layout_test_dir("no-tab-section-specified.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
let layout_template = layout_from_yaml.as_ref().unwrap();
let tab_layout = layout_template.template.clone().insert_tab_layout(None);
let merged_layout = Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: None,
run: None,
}],
split_size: None,
run: None,
};
assert_eq!(merged_layout, tab_layout.into());
}
#[test]
fn no_layout_template_specified_is_ok() {
let path = layout_test_dir("no-layout-template-specified.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
assert!(layout_from_yaml.is_ok());
}
#[test]
fn no_layout_template_has_one_tab() {
let path = layout_test_dir("no-layout-template-specified.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
let layout_template = layout_from_yaml.unwrap();
assert_eq!(layout_template.tabs.len(), 1);
}
#[test]
fn no_layout_template_merged_correctly() {
let path = layout_test_dir("no-layout-template-specified.yaml".into());
let layout_from_yaml = LayoutFromYaml::new(&path);
let layout_template = layout_from_yaml.as_ref().unwrap();
let tab_layout = layout_template
.template
.clone()
.insert_tab_layout(Some(layout_template.tabs[0].clone()));
let merged_layout = Layout {
direction: Direction::Horizontal,
parts: vec![Layout {
direction: Direction::Vertical,
parts: vec![
Layout {
direction: Direction::Horizontal,
parts: vec![],
split_size: None,
run: None,
borderless: false,
},
Layout {
direction: Direction::Horizontal,
parts: vec![],
split_size: None,
run: None,
borderless: false,
},
],
split_size: None,
run: None,
borderless: false,
}],
split_size: None,
run: None,
borderless: false,
};
assert_eq!(merged_layout, tab_layout.into());
}

View File

@ -1,18 +1,20 @@
//! IPC stuff for starting to split things into a client and server model. //! IPC stuff for starting to split things into a client and server model.
use crate::cli::CliArgs;
use crate::pane_size::PositionAndSize;
use crate::{ use crate::{
cli::CliArgs,
errors::{get_current_ctx, ErrorContext}, errors::{get_current_ctx, ErrorContext},
input::{actions::Action, layout::Layout, options::Options}, input::{actions::Action, layout::LayoutFromYaml, options::Options},
pane_size::PositionAndSize,
}; };
use interprocess::local_socket::LocalSocketStream; use interprocess::local_socket::LocalSocketStream;
use nix::unistd::dup; use nix::unistd::dup;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::{Display, Error, Formatter}; use std::{
use std::io::{self, Write}; fmt::{Display, Error, Formatter},
use std::marker::PhantomData; io::{self, Write},
use std::os::unix::io::{AsRawFd, FromRawFd}; marker::PhantomData,
os::unix::io::{AsRawFd, FromRawFd},
};
use zellij_tile::data::Palette; use zellij_tile::data::Palette;
@ -56,7 +58,12 @@ pub enum ClientToServerMsg {
// Disconnect from the session we're connected to // Disconnect from the session we're connected to
DisconnectFromSession,*/ DisconnectFromSession,*/
TerminalResize(PositionAndSize), TerminalResize(PositionAndSize),
NewClient(ClientAttributes, Box<CliArgs>, Box<Options>, Option<Layout>), NewClient(
ClientAttributes,
Box<CliArgs>,
Box<Options>,
Option<LayoutFromYaml>,
),
AttachClient(ClientAttributes, bool, Options), AttachClient(ClientAttributes, bool, Options),
Action(Action), Action(Action),
ClientExited, ClientExited,

View File

@ -1,12 +1,13 @@
use crate::consts::{
FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION, ZELLIJ_PROJ_DIR,
};
use crate::input::options::Options;
use crate::{ use crate::{
cli::{CliArgs, Command}, cli::{CliArgs, Command},
consts::{
FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION,
ZELLIJ_PROJ_DIR,
},
input::{ input::{
config::{Config, ConfigError}, config::{Config, ConfigError},
layout::Layout, layout::LayoutFromYaml,
options::Options,
}, },
}; };
use directories_next::BaseDirs; use directories_next::BaseDirs;
@ -150,7 +151,9 @@ impl Setup {
/// file options: /// file options:
/// 1. command line options (`zellij options`) /// 1. command line options (`zellij options`)
/// 2. config options (`config.yaml`) /// 2. config options (`config.yaml`)
pub fn from_options(opts: &CliArgs) -> Result<(Config, Option<Layout>, Options), ConfigError> { pub fn from_options(
opts: &CliArgs,
) -> Result<(Config, Option<LayoutFromYaml>, Options), ConfigError> {
let clean = match &opts.command { let clean = match &opts.command {
Some(Command::Setup(ref setup)) => setup.clean, Some(Command::Setup(ref setup)) => setup.clean,
_ => false, _ => false,
@ -160,7 +163,6 @@ impl Setup {
match Config::try_from(opts) { match Config::try_from(opts) {
Ok(config) => config, Ok(config) => config,
Err(e) => { Err(e) => {
eprintln!("There was an error in the config file:");
return Err(e); return Err(e);
} }
} }
@ -174,7 +176,7 @@ impl Setup {
.layout_dir .layout_dir
.clone() .clone()
.or_else(|| get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir))); .or_else(|| get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir)));
let layout_result = crate::input::layout::Layout::from_path_or_default( let layout_result = LayoutFromYaml::from_path_or_default(
opts.layout.as_ref(), opts.layout.as_ref(),
opts.layout_path.as_ref(), opts.layout_path.as_ref(),
layout_dir, layout_dir,
@ -183,10 +185,10 @@ impl Setup {
None => None, None => None,
Some(Ok(layout)) => Some(layout), Some(Ok(layout)) => Some(layout),
Some(Err(e)) => { Some(Err(e)) => {
eprintln!("There was an error in the layout file:");
return Err(e); return Err(e);
} }
}; };
//.map(|layout| layout.template);
if let Some(Command::Setup(ref setup)) = &opts.command { if let Some(Command::Setup(ref setup)) = &opts.command {
setup.from_cli(opts, &config_options).map_or_else( setup.from_cli(opts, &config_options).map_or_else(