aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2022-12-24 15:48:04 +0100
committerGitHub <noreply@github.com>2022-12-24 15:48:04 +0100
commit799fa5de8dfd0b826be0f903796bba48a470127e (patch)
tree8c202e116b757162cadd0cc350da256b9741e248
parentdocs(changelog): remove nix support (diff)
downloadsl0ppij-799fa5de8dfd0b826be0f903796bba48a470127e.tar.gz
sl0ppij-799fa5de8dfd0b826be0f903796bba48a470127e.tar.xz
sl0ppij-799fa5de8dfd0b826be0f903796bba48a470127e.zip
Floating panes in layouts (#2047)
* work * tests passing * tests: floating panes in layouts * panes(plugins): floating plugins working * refactor(tab): layout applier * style(comment): remove outdated * style(fmt): rustfmt
-rw-r--r--default-plugins/status-bar/src/second_line.rs4
-rw-r--r--zellij-server/src/lib.rs13
-rw-r--r--zellij-server/src/panes/floating_panes/mod.rs49
-rw-r--r--zellij-server/src/panes/tiled_panes/mod.rs22
-rw-r--r--zellij-server/src/plugins/mod.rs17
-rw-r--r--zellij-server/src/pty.rs378
-rw-r--r--zellij-server/src/route.rs8
-rw-r--r--zellij-server/src/screen.rs33
-rw-r--r--zellij-server/src/tab/layout_applier.rs376
-rw-r--r--zellij-server/src/tab/mod.rs247
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__tab_with_layout_that_has_floating_panes.snap26
-rw-r--r--zellij-server/src/tab/unit/tab_integration_tests.rs58
-rw-r--r--zellij-server/src/tab/unit/tab_tests.rs17
-rw-r--r--zellij-server/src/unit/screen_tests.rs10
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap3
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap3
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap4
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap4
-rw-r--r--zellij-utils/src/input/actions.rs23
-rw-r--r--zellij-utils/src/input/layout.rs98
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs184
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap5
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap5
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap4
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap4
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap4
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap4
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap6
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap4
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap102
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap3
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap4
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap4
-rw-r--r--zellij-utils/src/kdl/kdl_layout_parser.rs800
-rw-r--r--zellij-utils/src/kdl/mod.rs2
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap3
68 files changed, 2051 insertions, 576 deletions
diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs
index 4422105d..9b2c23fb 100644
--- a/default-plugins/status-bar/src/second_line.rs
+++ b/default-plugins/status-bar/src/second_line.rs
@@ -179,7 +179,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> {
vec![
(s("Move focus"), s("Move"), focus_keys),
- (s("New"), s("New"), action_key(&km, &[A::NewTab(None, None), TO_NORMAL])),
+ (s("New"), s("New"), action_key(&km, &[A::NewTab(None, vec![], None), TO_NORMAL])),
(s("Close"), s("Close"), action_key(&km, &[A::CloseTab, TO_NORMAL])),
(s("Rename"), s("Rename"),
action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])),
@@ -253,7 +253,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> {
(s("Split down"), s("Down"), action_key(&km, &[A::NewPane(Some(Dir::Down), None), TO_NORMAL])),
(s("Split right"), s("Right"), action_key(&km, &[A::NewPane(Some(Dir::Right), None), TO_NORMAL])),
(s("Fullscreen"), s("Fullscreen"), action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])),
- (s("New tab"), s("New"), action_key(&km, &[A::NewTab(None, None), TO_NORMAL])),
+ (s("New tab"), s("New"), action_key(&km, &[A::NewTab(None, vec![], None), TO_NORMAL])),
(s("Rename tab"), s("Rename"),
action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])),
(s("Previous Tab"), s("Previous"), action_key(&km, &[A::GoToPreviousTab, TO_NORMAL])),
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 40c2edc2..bae871f4 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -332,7 +332,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
})
});
- let spawn_tabs = |tab_layout, tab_name| {
+ let spawn_tabs = |tab_layout, floating_panes_layout, tab_name| {
session_data
.read()
.unwrap()
@@ -342,6 +342,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.send_to_screen(ScreenInstruction::NewTab(
default_shell.clone(),
tab_layout,
+ floating_panes_layout,
tab_name,
client_id,
))
@@ -349,8 +350,12 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
};
if layout.has_tabs() {
- for (tab_name, tab_layout) in layout.tabs() {
- spawn_tabs(Some(tab_layout.clone()), tab_name);
+ for (tab_name, tab_layout, floating_panes_layout) in layout.tabs() {
+ spawn_tabs(
+ Some(tab_layout.clone()),
+ floating_panes_layout.clone(),
+ tab_name,
+ );
}
if let Some(focused_tab_index) = layout.focused_tab_index() {
@@ -367,7 +372,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap();
}
} else {
- spawn_tabs(None, None);
+ spawn_tabs(None, layout.floating_panes_template.clone(), None);
}
session_data
.read()
diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs
index fe98ee63..47b3e2f2 100644
--- a/zellij-server/src/panes/floating_panes/mod.rs
+++ b/zellij-server/src/panes/floating_panes/mod.rs
@@ -25,7 +25,8 @@ use zellij_utils::{
data::{ModeInfo, Style},
errors::prelude::*,
input::command::RunCommand,
- pane_size::{Offset, PaneGeom, Size, Viewport},
+ input::layout::FloatingPanesLayout,
+ pane_size::{Dimension, Offset, PaneGeom, Size, Viewport},
};
const RESIZE_INCREMENT_WIDTH: usize = 5;
@@ -224,9 +225,55 @@ impl FloatingPanes {
);
floating_pane_grid.find_room_for_new_pane()
}
+ pub fn position_floating_pane_layout(
+ &mut self,
+ floating_pane_layout: &FloatingPanesLayout,
+ ) -> PaneGeom {
+ let display_area = *self.display_area.borrow();
+ let viewport = *self.viewport.borrow();
+ let floating_pane_grid = FloatingPaneGrid::new(
+ &mut self.panes,
+ &mut self.desired_pane_positions,
+ display_area,
+ viewport,
+ );
+ let mut position = floating_pane_grid.find_room_for_new_pane().unwrap(); // TODO: no unwrap
+ if let Some(x) = &floating_pane_layout.x {
+ position.x = x.to_position(display_area.cols);
+ }
+ if let Some(y) = &floating_pane_layout.y {
+ position.y = y.to_position(display_area.rows);
+ }
+ if let Some(width) = &floating_pane_layout.width {
+ position.cols = Dimension::fixed(width.to_position(display_area.cols));
+ }
+ if let Some(height) = &floating_pane_layout.height {
+ position.rows = Dimension::fixed(height.to_position(display_area.rows));
+ }
+ if position.cols.as_usize() > display_area.cols {
+ position.cols = Dimension::fixed(display_area.cols);
+ }
+ if position.rows.as_usize() > display_area.rows {
+ position.rows = Dimension::fixed(display_area.rows);
+ }
+ if position.x + position.cols.as_usize() > display_area.cols {
+ position.x = position
+ .x
+ .saturating_sub((position.x + position.cols.as_usize()) - display_area.cols);
+ }
+ if position.y + position.rows.as_usize() > display_area.rows {
+ position.y = position
+ .y
+ .saturating_sub((position.y + position.rows.as_usize()) - display_area.rows);
+ }
+ position
+ }
pub fn first_floating_pane_id(&self) -> Option<PaneId> {
self.panes.keys().next().copied()
}
+ pub fn last_floating_pane_id(&self) -> Option<PaneId> {
+ self.panes.keys().last().copied()
+ }
pub fn first_active_floating_pane_id(&self) -> Option<PaneId> {
self.active_panes.values().next().copied()
}
diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs
index 726627f4..cad53e3f 100644
--- a/zellij-server/src/panes/tiled_panes/mod.rs
+++ b/zellij-server/src/panes/tiled_panes/mod.rs
@@ -539,10 +539,13 @@ impl TiledPanes {
viewport.cols = (viewport.cols as isize + column_difference) as usize;
display_area.cols = cols;
},
- Err(e) => {
- Err::<(), _>(anyError::msg(e))
- .context("failed to resize tab horizontally")
- .non_fatal();
+ Err(e) => match e.downcast_ref::<ZellijError>() {
+ Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout
+ _ => {
+ Err::<(), _>(anyError::msg(e))
+ .context("failed to resize tab horizontally")
+ .non_fatal();
+ },
},
};
match pane_grid.layout(SplitDirection::Vertical, rows) {
@@ -551,10 +554,13 @@ impl TiledPanes {
viewport.rows = (viewport.rows as isize + row_difference) as usize;
display_area.rows = rows;
},
- Err(e) => {
- Err::<(), _>(anyError::msg(e))
- .context("failed to resize tab vertically")
- .non_fatal();
+ Err(e) => match e.downcast_ref::<ZellijError>() {
+ Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout
+ _ => {
+ Err::<(), _>(anyError::msg(e))
+ .context("failed to resize tab vertically")
+ .non_fatal();
+ },
},
};
}
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index 100833ae..ba46a545 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -12,7 +12,7 @@ use zellij_utils::{
errors::{prelude::*, ContextType, PluginContext},
input::{
command::TerminalAction,
- layout::{Layout, PaneLayout, Run, RunPlugin, RunPluginLocation},
+ layout::{FloatingPanesLayout, Layout, PaneLayout, Run, RunPlugin, RunPluginLocation},
plugins::PluginsConfig,
},
pane_size::Size,
@@ -29,6 +29,7 @@ pub enum PluginInstruction {
NewTab(
Option<TerminalAction>,
Option<PaneLayout>,
+ Vec<FloatingPanesLayout>,
Option<String>, // tab name
usize, // tab_index
ClientId,
@@ -69,7 +70,6 @@ pub(crate) fn plugin_thread_main(
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin((&event).into()));
match event {
- // TODO: remove pid_tx from here
PluginInstruction::Load(run, tab_index, client_id, size) => {
wasm_bridge.load_plugin(&run, tab_index, size, client_id)?;
},
@@ -91,16 +91,22 @@ pub(crate) fn plugin_thread_main(
PluginInstruction::NewTab(
terminal_action,
tab_layout,
+ floating_panes_layout,
tab_name,
tab_index,
client_id,
) => {
let mut plugin_ids: HashMap<RunPluginLocation, Vec<u32>> = HashMap::new();
- let extracted_run_instructions = tab_layout
+ let mut extracted_run_instructions = tab_layout
.clone()
- .unwrap_or_else(|| layout.new_tab())
+ .unwrap_or_else(|| layout.new_tab().0)
.extract_run_instructions();
- let size = Size::default(); // TODO: is this bad?
+ let size = Size::default();
+ let mut extracted_floating_plugins: Vec<Option<Run>> = floating_panes_layout
+ .iter()
+ .map(|f| f.run.clone())
+ .collect();
+ extracted_run_instructions.append(&mut extracted_floating_plugins);
for run_instruction in extracted_run_instructions {
if let Some(Run::Plugin(run)) = run_instruction {
let plugin_id =
@@ -111,6 +117,7 @@ pub(crate) fn plugin_thread_main(
drop(bus.senders.send_to_pty(PtyInstruction::NewTab(
terminal_action,
tab_layout,
+ floating_panes_layout,
tab_name,
tab_index,
plugin_ids,
diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs
index 9f4cd3e3..04e3391f 100644
--- a/zellij-server/src/pty.rs
+++ b/zellij-server/src/pty.rs
@@ -15,7 +15,7 @@ use zellij_utils::{
errors::{ContextType, PtyContext},
input::{
command::{RunCommand, TerminalAction},
- layout::{Layout, PaneLayout, Run, RunPluginLocation},
+ layout::{FloatingPanesLayout, Layout, PaneLayout, Run, RunPluginLocation},
},
};
@@ -50,6 +50,7 @@ pub enum PtyInstruction {
NewTab(
Option<TerminalAction>,
Option<PaneLayout>,
+ Vec<FloatingPanesLayout>,
Option<String>,
usize, // tab_index
HashMap<RunPluginLocation, Vec<u32>>, // plugin_ids
@@ -335,6 +336,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
PtyInstruction::NewTab(
terminal_action,
tab_layout,
+ floating_panes_layout,
tab_name,
tab_index,
plugin_ids,
@@ -342,8 +344,14 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
) => {
let err_context = || format!("failed to open new tab for client {}", client_id);
+ let floating_panes_layout = if floating_panes_layout.is_empty() {
+ layout.new_tab().1
+ } else {
+ floating_panes_layout
+ };
pty.spawn_terminals_for_layout(
- tab_layout.unwrap_or_else(|| layout.new_tab()),
+ tab_layout.unwrap_or_else(|| layout.new_tab().0),
+ floating_panes_layout,
terminal_action.clone(),
plugin_ids,
tab_index,
@@ -562,6 +570,7 @@ impl Pty {
pub fn spawn_terminals_for_layout(
&mut self,
layout: PaneLayout,
+ floating_panes_layout: Vec<FloatingPanesLayout>,
default_shell: Option<TerminalAction>,
plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
tab_index: usize,
@@ -572,176 +581,28 @@ impl Pty {
let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal(None));
self.fill_cwd(&mut default_shell, client_id);
let extracted_run_instructions = layout.extract_run_instructions();
+ let extracted_floating_run_instructions =
+ floating_panes_layout.iter().map(|f| f.run.clone());
let mut new_pane_pids: Vec<(u32, bool, Option<RunCommand>, Result<RawFd>)> = vec![]; // (terminal_id,
// starts_held,
// run_command,
// file_descriptor)
+ let mut new_floating_panes_pids: Vec<(u32, bool, Option<RunCommand>, Result<RawFd>)> =
+ vec![]; // same
+ // as
+ // new_pane_pids
for run_instruction in extracted_run_instructions {
- let quit_cb = Box::new({
- let senders = self.bus.senders.clone();
- move |pane_id, _exit_status, _command| {
- let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
- }
- });
- match run_instruction {
- Some(Run::Command(command)) => {
- let starts_held = command.hold_on_start;
- let hold_on_close = command.hold_on_close;
- let quit_cb = Box::new({
- let senders = self.bus.senders.clone();
- move |pane_id, exit_status, command| {
- if hold_on_close {
- let _ = senders.send_to_screen(ScreenInstruction::HoldPane(
- pane_id,
- exit_status,
- command,
- None,
- ));
- } else {
- let _ = senders
- .send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
- }
- }
- });
- let cmd = TerminalAction::RunCommand(command.clone());
- if starts_held {
- // we don't actually open a terminal in this case, just wait for the user to run it
- match self
- .bus
- .os_input
- .as_mut()
- .context("no OS I/O interface found")
- .with_context(err_context)?
- .reserve_terminal_id()
- {
- Ok(terminal_id) => {
- new_pane_pids.push((
- terminal_id,
- starts_held,
- Some(command.clone()),
- Ok(terminal_id as i32), // this is not actually correct but gets
- // stripped later
- ));
- },
- Err(e) => Err::<(), _>(e).with_context(err_context).non_fatal(),
- }
- } else {
- match self
- .bus
- .os_input
- .as_mut()
- .context("no OS I/O interface found")
- .with_context(err_context)?
- .spawn_terminal(cmd, quit_cb, self.default_editor.clone())
- .with_context(err_context)
- {
- Ok((terminal_id, pid_primary, child_fd)) => {
- self.id_to_child_pid.insert(terminal_id, child_fd);
- new_pane_pids.push((
- terminal_id,
- starts_held,
- Some(command.clone()),
- Ok(pid_primary),
- ));
- },
- Err(err) => match err.downcast_ref::<ZellijError>() {
- Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
- new_pane_pids.push((
- *terminal_id,
- starts_held,
- Some(command.clone()),
- Err(err),
- ));
- },
- _ => {
- Err::<(), _>(err).non_fatal();
- },
- },
- }
- }
- },
- Some(Run::Cwd(cwd)) => {
- let starts_held = false; // we do not hold Cwd panes
- let shell = self.get_default_terminal(Some(cwd));
- match self
- .bus
- .os_input
- .as_mut()
- .context("no OS I/O interface found")
- .with_context(err_context)?
- .spawn_terminal(shell, quit_cb, self.default_editor.clone())
- .with_context(err_context)
- {
- Ok((terminal_id, pid_primary, child_fd)) => {
- self.id_to_child_pid.insert(terminal_id, child_fd);
- new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
- },
- Err(err) => match err.downcast_ref::<ZellijError>() {
- Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
- new_pane_pids.push((*terminal_id, starts_held, None, Err(err)));
- },
- _ => {
- Err::<(), _>(err).non_fatal();
- },
- },
- }
- },
- Some(Run::EditFile(path_to_file, line_number)) => {
- let starts_held = false; // we do not hold edit panes (for now?)
- match self
- .bus
- .os_input
- .as_mut()
- .context("no OS I/O interface found")
- .with_context(err_context)?
- .spawn_terminal(
- TerminalAction::OpenFile(path_to_file, line_number),
- quit_cb,
- self.default_editor.clone(),
- )
- .with_context(err_context)
- {
- Ok((terminal_id, pid_primary, child_fd)) => {
- self.id_to_child_pid.insert(terminal_id, child_fd);
- new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
- },
- Err(err) => match err.downcast_ref::<ZellijError>() {
- Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
- new_pane_pids.push((*terminal_id, starts_held, None, Err(err)));
- },
- _ => {
- Err::<(), _>(err).non_fatal();
- },
- },
- }
- },
- None => {
- let starts_held = false;
- match self
- .bus
- .os_input
- .as_mut()
- .context("no OS I/O interface found")
- .with_context(err_context)?
- .spawn_terminal(default_shell.clone(), quit_cb, self.default_editor.clone())
- .with_context(err_context)
- {
- Ok((terminal_id, pid_primary, child_fd)) => {
- self.id_to_child_pid.insert(terminal_id, child_fd);
- new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
- },
- Err(err) => match err.downcast_ref::<ZellijError>() {
- Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
- new_pane_pids.push((*terminal_id, starts_held, None, Err(err)));
- },
- _ => {
- Err::<(), _>(err).non_fatal();
- },
- },
- }
- },
- // Investigate moving plugin loading to here.
- Some(Run::Plugin(_)) => {},
+ if let Some(new_pane_data) =
+ self.apply_run_instruction(run_instruction, default_shell.clone())?
+ {
+ new_pane_pids.push(new_pane_data);
+ }
+ }
+ for run_instruction in extracted_floating_run_instructions {
+ if let Some(new_pane_data) =
+ self.apply_run_instruction(run_instruction, default_shell.clone())?
+ {
+ new_floating_panes_pids.push(new_pane_data);
}
}
// Option<RunCommand> should only be Some if the pane starts held
@@ -755,17 +616,32 @@ impl Pty {
}
})
.collect();
+ let new_tab_floating_pane_ids: Vec<(u32, Option<RunCommand>)> = new_floating_panes_pids
+ .iter()
+ .map(|(terminal_id, starts_held, run_command, _)| {
+ if *starts_held {
+ (*terminal_id, run_command.clone())
+ } else {
+ (*terminal_id, None)
+ }
+ })
+ .collect();
self.bus
.senders
.send_to_screen(ScreenInstruction::ApplyLayout(
layout,
+ floating_panes_layout,
new_tab_pane_ids,
+ new_tab_floating_pane_ids,
plugin_ids,
tab_index,
client_id,
))
.with_context(err_context)?;
- for (terminal_id, starts_held, run_command, pid_primary) in new_pane_pids {
+ let mut terminals_to_start = vec![];
+ terminals_to_start.append(&mut new_pane_pids);
+ terminals_to_start.append(&mut new_floating_panes_pids);
+ for (terminal_id, starts_held, run_command, pid_primary) in terminals_to_start {
if starts_held {
// we do not run a command or start listening for bytes on held panes
continue;
@@ -820,6 +696,172 @@ impl Pty {
}
Ok(())
}
+ fn apply_run_instruction(
+ &mut self,
+ run_instruction: Option<Run>,
+ default_shell: TerminalAction,
+ ) -> Result<Option<(u32, bool, Option<RunCommand>, Result<i32>)>> {
+ // terminal_id,
+ // starts_held,
+ // command
+ // successfully opened
+ let err_context = || format!("failed to apply run instruction");
+ let quit_cb = Box::new({
+ let senders = self.bus.senders.clone();
+ move |pane_id, _exit_status, _command| {
+ let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
+ }
+ });
+ match run_instruction {
+ Some(Run::Command(command)) => {
+ let starts_held = command.hold_on_start;
+ let hold_on_close = command.hold_on_close;
+ let quit_cb = Box::new({
+ let senders = self.bus.senders.clone();
+ move |pane_id, exit_status, command| {
+ if hold_on_close {
+ let _ = senders.send_to_screen(ScreenInstruction::HoldPane(
+ pane_id,
+ exit_status,
+ command,
+ None,
+ ));
+ } else {
+ let _ =
+ senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
+ }
+ }
+ });
+ let cmd = TerminalAction::RunCommand(command.clone());
+ if starts_held {
+ // we don't actually open a terminal in this case, just wait for the user to run it
+ match self
+ .bus
+ .os_input
+ .as_mut()
+ .context("no OS I/O interface found")
+ .with_context(err_context)?
+ .reserve_terminal_id()
+ {
+ Ok(terminal_id) => {
+ Ok(Some((
+ terminal_id,
+ starts_held,
+ Some(command.clone()),
+ Ok(terminal_id as i32), // this is not actually correct but gets
+ // stripped later
+ )))
+ },
+ Err(e) => Err(e),
+ }
+ } else {
+ match self
+ .bus
+ .os_input
+ .as_mut()
+ .context("no OS I/O interface found")
+ .with_context(err_context)?
+ .spawn_terminal(cmd, quit_cb, self.default_editor.clone())
+ .with_context(err_context)
+ {
+ Ok((terminal_id, pid_primary, child_fd)) => {
+ self.id_to_child_pid.insert(terminal_id, child_fd);
+ Ok(Some((
+ terminal_id,
+ starts_held,
+ Some(command.clone()),
+ Ok(pid_primary),
+ )))
+ },
+ Err(err) => {
+ match err.downcast_ref::<ZellijError>() {
+ Some(ZellijError::CommandNotFound { terminal_id, .. }) => Ok(Some(
+ (*terminal_id, starts_held, Some(command.clone()), Err(err)),
+ )),
+ _ => Err(err),
+ }
+ },
+ }
+ }
+ },
+ Some(Run::Cwd(cwd)) => {
+ let starts_held = false; // we do not hold Cwd panes
+ let shell = self.get_default_terminal(Some(cwd));
+ match self
+ .bus
+ .os_input
+ .as_mut()
+ .context("no OS I/O interface found")
+ .with_context(err_context)?
+ .spawn_terminal(shell, quit_cb, self.default_editor.clone())
+ .with_context(err_context)
+ {
+ Ok((terminal_id, pid_primary, child_fd)) => {
+ self.id_to_child_pid.insert(terminal_id, child_fd);
+ Ok(Some((terminal_id, starts_held, None, Ok(pid_primary))))
+ },
+ Err(err) => match err.downcast_ref::<ZellijError>() {
+ Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
+ Ok(Some((*terminal_id, starts_held, None, Err(err))))
+ },
+ _ => Err(err),
+ },
+ }
+ },
+ Some(Run::EditFile(path_to_file, line_number)) => {
+ let starts_held = false; // we do not hold edit panes (for now?)
+ match self
+ .bus
+ .os_input
+ .as_mut()
+ .context("no OS I/O interface found")
+ .with_context(err_context)?
+ .spawn_terminal(
+ TerminalAction::OpenFile(path_to_file, line_number),
+ quit_cb,
+ self.default_editor.clone(),
+ )
+ .with_context(err_context)
+ {
+ Ok((terminal_id, pid_primary, child_fd)) => {
+ self.id_to_child_pid.insert(terminal_id, child_fd);
+ Ok(Some((terminal_id, starts_held, None, Ok(pid_primary))))
+ },
+ Err(err) => match err.downcast_ref::<ZellijError>() {
+ Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
+ Ok(Some((*terminal_id, starts_held, None, Err(err))))
+ },
+ _ => Err(err),
+ },
+ }
+ },
+ None => {
+ let starts_held = false;
+ match self
+ .bus
+ .os_input
+ .as_mut()
+ .context("no OS I/O interface found")
+ .with_context(err_context)?
+ .spawn_terminal(default_shell.clone(), quit_cb, self.default_editor.clone())
+ .with_context(err_context)
+ {
+ Ok((terminal_id, pid_primary, child_fd)) => {
+ self.id_to_child_pid.insert(terminal_id, child_fd);
+ Ok(Some((terminal_id, starts_held, None, Ok(pid_primary))))
+ },
+ Err(err) => match err.downcast_ref::<ZellijError>() {
+ Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
+ Ok(Some((*terminal_id, starts_held, None, Err(err))))
+ },
+ _ => Err(err),
+ },
+ }
+ },
+ // Investigate moving plugin loading to here.
+ Some(Run::Plugin(_)) => Ok(None),
+ }
+ }
pub fn close_pane(&mut self, id: PaneId) -> Result<()> {
let err_context = || format!("failed to close for pane {id:?}");
match id {
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index 584bff3f..ea5f6693 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -430,12 +430,16 @@ pub(crate) fn route_action(
.send_to_screen(ScreenInstruction::CloseFocusedPane(client_id))
.with_context(err_context)?;
},
- Action::NewTab(tab_layout, tab_name) => {
+ Action::NewTab(tab_layout, floating_panes_layout, tab_name) => {
let shell = session.default_shell.clone();
session
.senders
.send_to_screen(ScreenInstruction::NewTab(
- shell, tab_layout, tab_name, client_id,
+ shell,
+ tab_layout,
+ floating_panes_layout,
+ tab_name,
+ client_id,
))
.with_context(err_context)?;
},
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index a862cb40..2c5e44b5 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -12,7 +12,7 @@ use zellij_utils::input::options::Clipboard;
use zellij_utils::pane_size::{Size, SizeInPixels};
use zellij_utils::{
input::command::TerminalAction,
- input::layout::{PaneLayout, RunPluginLocation},
+ input::layout::{FloatingPanesLayout, PaneLayout, RunPluginLocation},
position::Position,
};
@@ -177,12 +177,15 @@ pub enum ScreenInstruction {
NewTab(
Option<TerminalAction>,
Option<PaneLayout>,
+ Vec<FloatingPanesLayout>,
Option<String>,
ClientId,
),
ApplyLayout(
PaneLayout,
- Vec<(u32, HoldForCommand)>,
+ Vec<FloatingPanesLayout>,
+ Vec<(u32, HoldForCommand)>, // new pane pids
+ Vec<(u32, HoldForCommand)>, // new floating pane pids
HashMap<RunPluginLocation, Vec<u32>>,
usize, // tab_index
ClientId,
@@ -920,7 +923,9 @@ impl Screen {
pub fn apply_layout(
&mut self,
layout: PaneLayout,
+ floating_panes_layout: Vec<FloatingPanesLayout>,
new_terminal_ids: Vec<(u32, HoldForCommand)>,
+ new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
tab_index: usize,
client_id: ClientId,
@@ -968,9 +973,10 @@ impl Screen {
let tab = self.tabs.get_mut(&tab_index).unwrap(); // TODO: no unwrap
tab.apply_layout(
layout,
+ floating_panes_layout,
new_terminal_ids,
+ new_floating_terminal_ids,
new_plugin_ids,
- tab_index,
client_id,
)
.with_context(err_context)?;
@@ -1878,7 +1884,13 @@ pub(crate) fn screen_thread_main(
screen.unblock_input()?;
screen.render()?;
},
- ScreenInstruction::NewTab(default_shell, layout, tab_name, client_id) => {
+ ScreenInstruction::NewTab(
+ default_shell,
+ layout,
+ floating_panes_layout,
+ tab_name,
+ client_id,
+ ) => {
let tab_index = screen.get_new_tab_index();
screen.new_tab(tab_index, client_id)?;
screen
@@ -1887,6 +1899,7 @@ pub(crate) fn screen_thread_main(
.send_to_plugin(PluginInstruction::NewTab(
default_shell,
layout,
+ floating_panes_layout,
tab_name,
tab_index,
client_id,
@@ -1894,12 +1907,22 @@ pub(crate) fn screen_thread_main(
},
ScreenInstruction::ApplyLayout(
layout,
+ floating_panes_layout,
new_pane_pids,
+ new_floating_pane_pids,
new_plugin_ids,
tab_index,
client_id,
) => {
- screen.apply_layout(layout, new_pane_pids, new_plugin_ids, tab_index, client_id)?;
+ screen.apply_layout(
+ layout,
+ floating_panes_layout,
+ new_pane_pids,
+ new_floating_pane_pids,
+ new_plugin_ids,
+ tab_index,
+ client_id,
+ )?;
screen.unblock_input()?;
screen.render()?;
},
diff --git a/zellij-server/src/tab/layout_applier.rs b/zellij-server/src/tab/layout_applier.rs
new file mode 100644
index 00000000..a0b641df
--- /dev/null
+++ b/zellij-server/src/tab/layout_applier.rs
@@ -0,0 +1,376 @@
+use zellij_utils::errors::prelude::*;
+
+use crate::resize_pty;
+use crate::tab::{get_next_terminal_position, HoldForCommand, Pane};
+
+use crate::{
+ os_input_output::ServerOsApi,
+ panes::sixel::SixelImageStore,
+ panes::{FloatingPanes, TiledPanes},
+ panes::{LinkHandler, PaneId, PluginPane, TerminalPane},
+ plugins::PluginInstruction,
+ pty::PtyInstruction,
+ thread_bus::ThreadSenders,
+ ClientId,
+};
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::rc::Rc;
+use zellij_utils::{
+ data::{Palette, Style},
+ input::layout::{FloatingPanesLayout, PaneLayout, Run, RunPluginLocation},
+ pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
+};
+
+pub struct LayoutApplier<'a> {
+ viewport: Rc<RefCell<Viewport>>, // includes all non-UI panes
+ senders: ThreadSenders,
+ sixel_image_store: Rc<RefCell<SixelImageStore>>,
+ link_handler: Rc<RefCell<LinkHandler>>,
+ terminal_emulator_colors: Rc<RefCell<Palette>>,
+ terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
+ character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
+ style: Style,
+ display_area: Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout)
+ tiled_panes: &'a mut TiledPanes,
+ floating_panes: &'a mut FloatingPanes,
+ draw_pane_frames: bool,
+ focus_pane_id: &'a mut Option<PaneId>,
+ os_api: Box<dyn ServerOsApi>,
+}
+
+impl<'a> LayoutApplier<'a> {
+ pub fn new(
+ viewport: &Rc<RefCell<Viewport>>,
+ senders: &ThreadSenders,
+ sixel_image_store: &Rc<RefCell<SixelImageStore>>,
+ link_handler: &Rc<RefCell<LinkHandler>>,
+ terminal_emulator_colors: &Rc<RefCell<Palette>>,
+ terminal_emulator_color_codes: &Rc<RefCell<HashMap<usize, String>>>,
+ character_cell_size: &Rc<RefCell<Option<SizeInPixels>>>,
+ style: &Style,
+ display_area: &Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout)
+ tiled_panes: &'a mut TiledPanes,
+ floating_panes: &'a mut FloatingPanes,
+ draw_pane_frames: bool,
+ focus_pane_id: &'a mut Option<PaneId>,
+ os_api: &Box<dyn ServerOsApi>,
+ ) -> Self {
+ let viewport = viewport.clone();
+ let senders = senders.clone();
+ let sixel_image_store = sixel_image_store.clone();
+ let link_handler = link_handler.clone();
+ let terminal_emulator_colors = terminal_emulator_colors.clone();
+ let terminal_emulator_color_codes = terminal_emulator_color_codes.clone();
+ let character_cell_size = character_cell_size.clone();
+ let style = style.clone();
+ let display_area = display_area.clone();
+ let os_api = os_api.clone();
+ LayoutApplier {
+ viewport,
+ senders,
+ sixel_image_store,
+ link_handler,
+ terminal_emulator_colors,
+ terminal_emulator_color_codes,
+ character_cell_size,
+ style,
+ display_area,
+ tiled_panes,
+ floating_panes,
+ draw_pane_frames,
+ focus_pane_id,
+ os_api,
+ }
+ }
+ pub fn apply_layout(
+ &mut self,
+ layout: PaneLayout,
+ floating_panes_layout: Vec<FloatingPanesLayout>,
+ new_terminal_ids: Vec<(u32, HoldForCommand)>,
+ new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
+ mut new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
+ client_id: ClientId,
+ ) -> Result<bool> {
+ // true => layout has floating panes
+ let layout_name = layout.name.clone();
+ self.apply_tiled_panes_layout(layout, new_terminal_ids, &mut new_plugin_ids, client_id)?;
+ let layout_has_floating_panes = self.apply_floating_panes_layout(
+ floating_panes_layout,
+ new_floating_terminal_ids,
+ &mut new_plugin_ids,
+ layout_name,
+ )?;
+ return Ok(layout_has_floating_panes);
+ }
+ fn apply_tiled_panes_layout(
+ &mut self,
+ layout: PaneLayout,
+ new_terminal_ids: Vec<(u32, HoldForCommand)>,
+ new_plugin_ids: &mut HashMap<RunPluginLocation, Vec<u32>>,
+ client_id: ClientId,
+ ) -> Result<()> {
+ let err_context = || format!("failed to apply tiled panes layout");
+ let (viewport_cols, viewport_rows) = {
+ let viewport = self.viewport.borrow();
+ (viewport.cols, viewport.rows)
+ };
+ let mut free_space = PaneGeom::default();
+ free_space.cols.set_inner(viewport_cols);
+ free_space.rows.set_inner(viewport_rows);
+ match layout.position_panes_in_space(&free_space) {
+ Ok(positions_in_layout) => {
+ let positions_and_size = positions_in_layout.iter();
+ let mut new_terminal_ids = new_terminal_ids.iter();
+
+ let mut focus_pane_id: Option<PaneId> = None;
+ let mut set_focus_pane_id = |layout: &PaneLayout, pane_id: PaneId| {
+ if layout.focus.unwrap_or(false) && focus_pane_id.is_none() {
+ focus_pane_id = Some(pane_id);
+ }
+ };
+
+ for (layout, position_and_size) in positions_and_size {
+ // A plugin pane
+ if let Some(Run::Plugin(run)) = layout.run.clone() {
+ let pane_title = run.location.to_string();
+ let pid = new_plugin_ids
+ .get_mut(&run.location)
+ .and_then(|ids| ids.pop())
+ .with_context(err_context)?;
+ let mut new_plugin = PluginPane::new(
+ pid,
+ *position_and_size,
+ self.senders
+ .to_plugin
+ .as_ref()
+ .with_context(err_context)?
+ .clone(),
+ pane_title,
+ layout.name.clone().unwrap_or_default(),
+ self.sixel_image_store.clone(),
+ self.terminal_emulator_colors.clone(),
+ self.terminal_emulator_color_codes.clone(),
+ self.link_handler.clone(),
+ self.character_cell_size.clone(),
+ self.style,
+ );
+ new_plugin.set_borderless(layout.borderless);
+ self.tiled_panes
+ .add_pane_with_existing_geom(PaneId::Plugin(pid), Box::new(new_plugin));
+ set_focus_pane_id(layout, PaneId::Plugin(pid));
+ } else {
+ // there are still panes left to fill, use the pids we received in this method
+ if let Some((pid, hold_for_command)) = new_terminal_ids.next() {
+ let next_terminal_position =
+ get_next_terminal_position(&self.tiled_panes, &self.floating_panes);
+ let initial_title = match &layout.run {
+ Some(Run::Command(run_command)) => Some(run_command.to_string()),
+ _ => None,
+ };
+ let mut new_pane = TerminalPane::new(
+ *pid,
+ *position_and_size,
+ self.style,
+ next_terminal_position,
+ layout.name.clone().unwrap_or_default(),
+ self.link_handler.clone(),
+ self.character_cell_size.clone(),
+ self.sixel_image_store.clone(),
+ self.terminal_emulator_colors.clone(),
+ self.terminal_emulator_color_codes.clone(),
+ initial_title,
+ );
+ new_pane.set_borderless(layout.borderless);
+ if let Some(held_command) = hold_for_command {
+ new_pane.hold(None, true, held_command.clone());
+ }
+ self.tiled_panes.add_pane_with_existing_geom(
+ PaneId::Terminal(*pid),
+ Box::new(new_pane),
+ );
+ set_focus_pane_id(layout, PaneId::Terminal(*pid));
+ }
+ }
+ }
+ for (unused_pid, _) in new_terminal_ids {
+ self.senders
+ .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
+ .with_context(err_context)?;
+ }
+ self.adjust_viewport();
+ self.set_focused_tiled_pane(focus_pane_id, client_id);
+ },
+ Err(e) => {
+ for (unused_pid, _) in new_terminal_ids {
+ self.senders
+ .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(unused_pid)))
+ .with_context(err_context)?;
+ }
+ Err::<(), _>(anyError::msg(e))
+ .with_context(err_context)
+ .non_fatal(); // TODO: propagate this to the user
+ },
+ };
+ Ok(())
+ }
+ fn apply_floating_panes_layout(
+ &mut self,
+ floating_panes_layout: Vec<FloatingPanesLayout>,
+ new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
+ new_plugin_ids: &mut HashMap<RunPluginLocation, Vec<u32>>,
+ layout_name: Option<String>,
+ ) -> Result<bool> {
+ // true => has floating panes
+ let err_context = || format!("Failed to apply_floating_panes_layout");
+ let mut layout_has_floating_panes = false;
+ let floating_panes_layout = floating_panes_layout.iter();
+ let mut focused_floating_pane = None;
+ let mut new_floating_terminal_ids = new_floating_terminal_ids.iter();
+ for floating_pane_layout in floating_panes_layout {
+ layout_has_floating_panes = true;
+ if let Some(Run::Plugin(run)) = floating_pane_layout.run.clone() {
+ let position_and_size = self
+ .floating_panes
+ .position_floating_pane_layout(&floating_pane_layout);
+ let pane_title = run.location.to_string();
+ let pid = new_plugin_ids
+ .get_mut(&run.location)
+ .and_then(|ids| ids.pop())
+ .with_context(err_context)?;
+ let mut new_pane = PluginPane::new(
+ pid,
+ position_and_size,
+ self.senders
+ .to_plugin
+ .as_ref()
+ .with_context(err_context)?
+ .clone(),
+ pane_title,
+ layout_name.clone().unwrap_or_default(),
+ self.sixel_image_store.clone(),
+ self.terminal_emulator_colors.clone(),
+ self.terminal_emulator_color_codes.clone(),
+ self.link_handler.clone(),
+ self.character_cell_size.clone(),
+ self.style,
+ );
+ new_pane.set_borderless(false);
+ new_pane.set_content_offset(Offset::frame(1));
+ resize_pty!(new_pane, self.os_api, self.senders)?;
+ self.floating_panes
+ .add_pane(PaneId::Plugin(pid), Box::new(new_pane));
+ if floating_pane_layout.focus.unwrap_or(false) {
+ focused_floating_pane = Some(PaneId::Plugin(pid));
+ }
+ } else if let Some((pid, hold_for_command)) = new_floating_terminal_ids.next() {
+ let position_and_size = self
+ .floating_panes
+ .position_floating_pane_layout(&floating_pane_layout);
+ let next_terminal_position =
+ get_next_terminal_position(&self.tiled_panes, &self.floating_panes);
+ let initial_title = match &floating_pane_layout.run {
+ Some(Run::Command(run_command)) => Some(run_command.to_string()),
+ _ => None,
+ };
+ let mut new_pane = TerminalPane::new(
+ *pid,
+ position_and_size,
+ self.style,
+ next_terminal_position,
+ floating_pane_layout.name.clone().unwrap_or_default(),
+ self.link_handler.clone(),
+ self.character_cell_size.clone(),
+ self.sixel_image_store.clone(),
+ self.terminal_emulator_colors.clone(),
+ self.terminal_emulator_color_codes.clone(),
+ initial_title,
+ );
+ new_pane.set_borderless(false);
+ new_pane.set_content_offset(Offset::frame(1));
+ if let Some(held_command) = hold_for_command {
+ new_pane.hold(None, true, held_command.clone());
+ }
+ resize_pty!(new_pane, self.os_api, self.senders)?;
+ self.floating_panes
+ .add_pane(PaneId::Terminal(*pid), Box::new(new_pane));
+ if floating_pane_layout.focus.unwrap_or(false) {
+ focused_floating_pane = Some(PaneId::Terminal(*pid));
+ }
+ }
+ }
+ if let Some(focused_floating_pane) = focused_floating_pane {
+ self.floating_panes
+ .focus_pane_for_all_clients(focused_floating_pane);
+ }
+ if layout_has_floating_panes {
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+ fn resize_whole_tab(&mut self, new_screen_size: Size) {
+ self.floating_panes.resize(new_screen_size);
+ self.floating_panes
+ .resize_pty_all_panes(&mut self.os_api)
+ .unwrap(); // we need to do this explicitly because floating_panes.resize does not do this
+ self.tiled_panes.resize(new_screen_size);
+ }
+ fn offset_viewport(&mut self, position_and_size: &Viewport) {
+ let mut viewport = self.viewport.borrow_mut();
+ if position_and_size.x == viewport.x
+ && position_and_size.x + position_and_size.cols == viewport.x + viewport.cols
+ {
+ if position_and_size.y == viewport.y {
+ viewport.y += position_and_size.rows;
+ viewport.rows -= position_and_size.rows;
+ } else if position_and_size.y + position_and_size.rows == viewport.y + viewport.rows {
+ viewport.rows -= position_and_size.rows;
+ }
+ }
+ if position_and_size.y == viewport.y
+ && position_and_size.y + position_and_size.rows == viewport.y + viewport.rows
+ {
+ if position_and_size.x == viewport.x {
+ viewport.x += position_and_size.cols;
+ viewport.cols -= position_and_size.cols;
+ } else if position_and_size.x + position_and_size.cols == viewport.x + viewport.cols {
+ viewport.cols -= position_and_size.cols;
+ }
+ }
+ }
+ fn adjust_viewport(&mut self) {
+ // here we offset the viewport after applying a tiled panes layout
+ // from borderless panes that are on the edges of the
+ // screen, this is so that when we don't have pane boundaries (eg. when they were
+ // disabled by the user) boundaries won't be drawn around these panes
+ // geometrically, we can only do this with panes that are on the edges of the
+ // screen - so it's mostly a best-effort thing
+ let display_area = {
+ let display_area = self.display_area.borrow();
+ *display_area
+ };
+ self.resize_whole_tab(display_area);
+ let boundary_geoms = self.tiled_panes.borderless_pane_geoms();
+ for geom in boundary_geoms {
+ self.offset_viewport(&geom)
+ }
+ self.tiled_panes.set_pane_frames(self.draw_pane_frames);
+ }
+ fn set_focused_tiled_pane(&mut self, focus_pane_id: Option<PaneId>, client_id: ClientId) {
+ if let Some(pane_id) = focus_pane_id {
+ *self.focus_pane_id = Some(pane_id);
+ self.tiled_panes.focus_pane(pane_id, client_id);
+ } else {
+ let next_selectable_pane_id = self.tiled_panes.first_selectable_pane_id();
+ match next_selectable_pane_id {
+ Some(active_pane_id) => {
+ self.tiled_panes.focus_pane(active_pane_id, client_id);
+ },
+ None => {
+ self.tiled_panes.clear_active_panes();
+ },
+ }
+ }
+ }
+}
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index c2b26ecf..0cf3a697 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -3,6 +3,7 @@
mod clipboard;
mod copy_command;
+mod layout_applier;
use copy_command::CopyCommand;
use std::env::temp_dir;
@@ -17,6 +18,7 @@ use crate::background_jobs::BackgroundJob;
use crate::pty_writer::PtyWriteInstruction;
use crate::screen::CopyOptions;
use crate::ui::pane_boundaries_frame::FrameParams;
+use layout_applier::LayoutApplier;
use self::clipboard::ClipboardProvider;
use crate::{
@@ -24,7 +26,7 @@ use crate::{
output::{CharacterChunk, Output, SixelImageChunk},
panes::sixel::SixelImageStore,
panes::{FloatingPanes, TiledPanes},
- panes::{LinkHandler, PaneId, PluginPane, TerminalPane},
+ panes::{LinkHandler, PaneId, TerminalPane},
plugins::PluginInstruction,
pty::{ClientOrTabIndex, PtyInstruction, VteBytes},
thread_bus::ThreadSenders,
@@ -42,7 +44,7 @@ use zellij_utils::{
data::{Event, InputMode, ModeInfo, Palette, PaletteColor, Style},
input::{
command::TerminalAction,
- layout::{PaneLayout, Run, RunPluginLocation},
+ layout::{FloatingPanesLayout, PaneLayout, RunPluginLocation},
parse_keys,
},
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
@@ -398,6 +400,26 @@ pub enum AdjustedInput {
ReRunCommandInThisPane(RunCommand),
CloseThisPane,
}
+pub fn get_next_terminal_position(
+ tiled_panes: &TiledPanes,
+ floating_panes: &FloatingPanes,
+) -> usize {
+ let tiled_panes_count = tiled_panes
+ .get_panes()
+ .filter(|(k, _)| match k {
+ PaneId::Plugin(_) => false,
+ PaneId::Terminal(_) => true,
+ })
+ .count();
+ let floating_panes_count = floating_panes
+ .get_panes()
+ .filter(|(k, _)| match k {
+ PaneId::Plugin(_) => false,
+ PaneId::Terminal(_) => true,
+ })
+ .count();
+ tiled_panes_count + floating_panes_count + 1
+}
impl Tab {
// FIXME: Still too many arguments for clippy to be happy...
@@ -509,170 +531,45 @@ impl Tab {
pub fn apply_layout(
&mut self,
layout: PaneLayout,
+ floating_panes_layout: Vec<FloatingPanesLayout>,
new_terminal_ids: Vec<(u32, HoldForCommand)>,
- mut new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
- tab_index: usize,
+ new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
+ new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
client_id: ClientId,
) -> Result<()> {
- let err_context = || {
- format!(
- "failed to apply layout {layout:#?} in tab {tab_index} for client id {client_id}"
- )
- };
-
- if self.tiled_panes.has_panes() {
- Err::<(), _>(anyhow!(
- "Applying a layout to a tab with existing panes - this is not yet supported!"
- ))
- .with_context(err_context)
- .non_fatal();
- }
- let (viewport_cols, viewport_rows) = {
- let viewport = self.viewport.borrow();
- (viewport.cols, viewport.rows)
- };
- let mut free_space = PaneGeom::default();
- free_space.cols.set_inner(viewport_cols);
- free_space.rows.set_inner(viewport_rows);
-
- match layout.position_panes_in_space(&free_space) {
- Ok(positions_in_layout) => {
- let positions_and_size = positions_in_layout.iter();
- let mut new_terminal_ids = new_terminal_ids.iter();
-
- let mut focus_pane_id: Option<PaneId> = None;
- let mut set_focus_pane_id = |layout: &PaneLayout, pane_id: PaneId| {
- if layout.focus.unwrap_or(false) && focus_pane_id.is_none() {
- focus_pane_id = Some(pane_id);
- }
- };
-
- for (layout, position_and_size) in positions_and_size {
- // A plugin pane
- if let Some(Run::Plugin(run)) = layout.run.clone() {
- // let (pid_tx, pid_rx) = channel();
- let pane_title = run.location.to_string();
- let pid = new_plugin_ids
- .get_mut(&run.location)
- .unwrap()
- .pop()
- .unwrap(); // TODO:
- // err_context
- // and
- // stuff
- let mut new_plugin = PluginPane::new(
- pid,
- *position_and_size,
- self.senders
- .to_plugin
- .as_ref()
- .with_context(err_context)?
- .clone(),
- pane_title,
- layout.name.clone().unwrap_or_default(),
- self.sixel_image_store.clone(),
- self.terminal_emulator_colors.clone(),
- self.terminal_emulator_color_codes.clone(),
- self.link_handler.clone(),
- self.character_cell_size.clone(),
- self.style,
- );
- new_plugin.set_borderless(layout.borderless);
- self.tiled_panes
- .add_pane_with_existing_geom(PaneId::Plugin(pid), Box::new(new_plugin));
- set_focus_pane_id(layout, PaneId::Plugin(pid));
- } else {
- // there are still panes left to fill, use the pids we received in this method
- if let Some((pid, hold_for_command)) = new_terminal_ids.next() {
- let next_terminal_position = self.get_next_terminal_position();
- let initial_title = match &layout.run {
- Some(Run::Command(run_command)) => Some(run_command.to_string()),
- _ => None,
- };
- let mut new_pane = TerminalPane::new(
- *pid,
- *position_and_size,
- self.style,
- next_terminal_position,
- layout.name.clone().unwrap_or_default(),
- self.link_handler.clone(),
- self.character_cell_size.clone(),
- self.sixel_image_store.clone(),
- self.terminal_emulator_colors.clone(),
- self.terminal_emulator_color_codes.clone(),
- initial_title,
- );
- new_pane.set_borderless(layout.borderless);
- if let Some(held_command) = hold_for_command {
- new_pane.hold(None, true, held_command.clone());
- }
- self.tiled_panes.add_pane_with_existing_geom(
- PaneId::Terminal(*pid),
- Box::new(new_pane),
- );
- set_focus_pane_id(layout, PaneId::Terminal(*pid));
- }
- }
- }
- for (unused_pid, _) in new_terminal_ids {
- // this is a bit of a hack and happens because we don't have any central location that
- // can query the screen as to how many panes it needs to create a layout
- // fixing this will require a bit of an architecture change
- self.senders
- .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
- .with_context(err_context)?;
- }
-
- // here we offset the viewport from borderless panes that are on the edges of the
- // screen, this is so that when we don't have pane boundaries (eg. when they were
- // disabled by the user) boundaries won't be drawn around these panes
- // geometrically, we can only do this with panes that are on the edges of the
- // screen - so it's mostly a best-effort thing
- let display_area = {
- let display_area = self.display_area.borrow();
- *display_area
- };
- self.resize_whole_tab(display_area);
- let boundary_geoms = self.tiled_panes.borderless_pane_geoms();
- for geom in boundary_geoms {
- self.offset_viewport(&geom)
- }
- self.tiled_panes.set_pane_frames(self.draw_pane_frames);
- self.should_clear_display_before_rendering = true;
-
- if let Some(pane_id) = focus_pane_id {
- self.focus_pane_id = Some(pane_id);
- self.tiled_panes.focus_pane(pane_id, client_id);
- } else {
- // This is the end of the nasty viewport hack...
- let next_selectable_pane_id = self.tiled_panes.first_selectable_pane_id();
- match next_selectable_pane_id {
- Some(active_pane_id) => {
- self.tiled_panes.focus_pane(active_pane_id, client_id);
- },
- None => {
- // this is very likely a configuration error (layout with no selectable panes)
- self.tiled_panes.clear_active_panes();
- },
- }
- }
- self.is_pending = false;
- self.apply_buffered_instructions()?;
- Ok(())
- },
- Err(e) => {
- for (unused_pid, _) in new_terminal_ids {
- self.senders
- .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(unused_pid)))
- .with_context(err_context)?;
- }
- self.is_pending = false;
- Err::<(), _>(anyError::msg(e))
- .with_context(err_context)
- .non_fatal(); // TODO: propagate this to the user
- Ok(())
- },
+ let layout_has_floating_panes = LayoutApplier::new(
+ &self.viewport,
+ &self.senders,
+ &self.sixel_image_store,
+ &self.link_handler,
+ &self.terminal_emulator_colors,
+ &self.terminal_emulator_color_codes,
+ &self.character_cell_size,
+ &self.style,
+ &self.display_area,
+ &mut self.tiled_panes,
+ &mut self.floating_panes,
+ self.draw_pane_frames,
+ &mut self.focus_pane_id,
+ &self.os_api,
+ )
+ .apply_layout(
+ layout,
+ floating_panes_layout,
+ new_terminal_ids,
+ new_floating_terminal_ids,
+ new_plugin_ids,
+ client_id,
+ )?;
+ if layout_has_floating_panes {
+ if !self.floating_panes.panes_are_visible() {
+ self.toggle_floating_panes(client_id, None)?;
+ }
}
+ self.tiled_panes.set_pane_frames(self.draw_pane_frames);
+ self.is_pending = false;
+ self.apply_buffered_instructions()?;
+ Ok(())
}
pub fn apply_buffered_instructions(&mut self) -> Result<()> {
let buffered_instructions: Vec<BufferedTabInstruction> =
@@ -852,7 +749,7 @@ impl Tab {
self.set_force_render();
} else {
self.show_floating_panes();
- match self.floating_panes.first_floating_pane_id() {
+ match self.floating_panes.last_floating_pane_id() {
Some(first_floating_pane_id) => {
if !self.floating_panes.active_panes_contain(&client_id) {
self.floating_panes
@@ -2708,30 +2605,6 @@ impl Tab {
Ok(())
}
- fn offset_viewport(&mut self, position_and_size: &Viewport) {
- let mut viewport = self.viewport.borrow_mut();
- if position_and_size.x == viewport.x
- && position_and_size.x + position_and_size.cols == viewport.x + viewport.cols
- {
- if position_and_size.y == viewport.y {
- viewport.y += position_and_size.rows;
- viewport.rows -= position_and_size.rows;
- } else if position_and_size.y + position_and_size.rows == viewport.y + viewport.rows {
- viewport.rows -= position_and_size.rows;
- }
- }
- if position_and_size.y == viewport.y
- && position_and_size.y + position_and_size.rows == viewport.y + viewport.rows
- {
- if position_and_size.x == viewport.x {
- viewport.x += position_and_size.cols;
- viewport.cols -= position_and_size.cols;
- } else if position_and_size.x + position_and_size.cols == viewport.x + viewport.cols {
- viewport.cols -= position_and_size.cols;
- }
- }
- }
-
pub fn visible(&self, visible: bool) -> Result<()> {
let pids_in_this_tab = self.tiled_panes.pane_ids().filter_map(|p| match p {
PaneId::Plugin(pid) => Some(pid),
diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__tab_with_layout_that_has_floating_panes.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__tab_with_layout_that_has_floating_panes.snap
new file mode 100644
index 00000000..6816fcb8
--- /dev/null
+++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__tab_with_layout_that_has_floating_panes.snap
@@ -0,0 +1,26 @@
+---
+source: zellij-server/src/tab/./unit/tab_integration_tests.rs
+assertion_line: 2560
+expression: snapshot
+---
+00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
+01 (C): │ │
+02 (C): │ │
+03 (C): │ │
+04 (C): │ │
+05 (C): │ ┌ Pane #2 ─────────────────────────────────────────────────┐ │
+06 (C): │ │ │ │
+07 (C): │ │ ┌ Pane #3 ─────────────────────────────────────────────────┐ │
+08 (C): │ │ │ │ │
+09 (C): │ │ │ │ │
+10 (C): │ │ │ │ │
+11 (C): │ │ │ │ │
+12 (C): │ │ │ │ │
+13 (C): │ │ │ │ │
+14 (C): │ └─│ │ │
+15 (C): │ │ │ │
+16 (C): │ └──────────────────────────────────────────────────────────┘ │
+17 (C): │ │
+18 (C): │ │
+19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
+
diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs
index 81be51cc..21f1fe14 100644
--- a/zellij-server/src/tab/unit/tab_integration_tests.rs
+++ b/zellij-server/src/tab/unit/tab_integration_tests.rs
@@ -229,9 +229,10 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
);
tab.apply_layout(
PaneLayout::default(),
+ vec![],
vec![(1, None)],
+ vec![],
HashMap::new(),
- index,
client_id,
)
.unwrap();
@@ -285,9 +286,10 @@ fn create_new_tab_with_os_api(
);
tab.apply_layout(
PaneLayout::default(),
+ vec![],
vec![(1, None)],
+ vec![],
HashMap::new(),
- index,
client_id,
)
.unwrap();
@@ -316,7 +318,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let layout = Layout::from_str(layout, "layout_file_name".into(), None).unwrap();
- let tab_layout = layout.new_tab();
+ let (tab_layout, floating_panes_layout) = layout.new_tab();
let mut tab = Tab::new(
index,
position,
@@ -343,8 +345,20 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
.enumerate()
.map(|(i, _)| (i as u32, None))
.collect();
- tab.apply_layout(tab_layout, pane_ids, HashMap::new(), index, client_id)
- .unwrap();
+ let floating_pane_ids = floating_panes_layout
+ .iter()
+ .enumerate()
+ .map(|(i, _)| (i as u32, None))
+ .collect();
+ tab.apply_layout(
+ tab_layout,
+ floating_panes_layout,
+ pane_ids,
+ floating_pane_ids,
+ HashMap::new(),
+ client_id,
+ )
+ .unwrap();
tab
}
@@ -396,9 +410,10 @@ fn create_new_tab_with_mock_pty_writer(
);
tab.apply_layout(
PaneLayout::default(),
+ vec![],
vec![(1, None)],
+ vec![],
HashMap::new(),
- index,
client_id,
)
.unwrap();
@@ -455,9 +470,10 @@ fn create_new_tab_with_sixel_support(
);
tab.apply_layout(
PaneLayout::default(),
+ vec![],
vec![(1, None)],
+ vec![],
HashMap::new(),
- index,
client_id,
)
.unwrap();
@@ -2520,6 +2536,34 @@ fn tab_with_basic_layout() {
}
#[test]
+fn tab_with_layout_that_has_floating_panes() {
+ let layout = r#"
+ layout {
+ pane
+ floating_panes {
+ pane
+ pane
+ }
+ }
+ "#;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let client_id = 1;
+ let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout);
+ let mut output = Output::default();
+ tab.render(&mut output, None).unwrap();
+ let snapshot = take_snapshot(
+ output.serialize().unwrap().get(&client_id).unwrap(),
+ size.rows,
+ size.cols,
+ Palette::default(),
+ );
+ assert_snapshot!(snapshot);
+}
+
+#[test]
fn tab_with_nested_layout() {
let layout = r#"
layout {
diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs
index 86848045..0fd61245 100644
--- a/zellij-server/src/tab/unit/tab_tests.rs
+++ b/zellij-server/src/tab/unit/tab_tests.rs
@@ -178,9 +178,10 @@ fn create_new_tab(size: Size) -> Tab {
);
tab.apply_layout(
PaneLayout::default(),
+ vec![],
vec![(1, None)],
+ vec![],
HashMap::new(),
- index,
client_id,
)
.unwrap();
@@ -231,8 +232,15 @@ fn create_new_tab_with_layout(size: Size, layout: PaneLayout) -> Tab {
for i in 0..layout.extract_run_instructions().len() {
new_terminal_ids.push((i as u32, None));
}
- tab.apply_layout(layout, new_terminal_ids, HashMap::new(), index, client_id)
- .unwrap();
+ tab.apply_layout(
+ layout,
+ vec![],
+ new_terminal_ids,
+ vec![],
+ HashMap::new(),
+ client_id,
+ )
+ .unwrap();
tab
}
@@ -280,9 +288,10 @@ fn create_new_tab_with_cell_size(
);
tab.apply_layout(
PaneLayout::default(),
+ vec![],
vec![(1, None)],
+ vec![],
HashMap::new(),
- index,
client_id,
)
.unwrap();
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index f6f993c7..dd212c32 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -14,7 +14,7 @@ use zellij_utils::data::Resize;
use zellij_utils::errors::{prelude::*, ErrorContext};
use zellij_utils::input::actions::Action;
use zellij_utils::input::command::{RunCommand, TerminalAction};
-use zellij_utils::input::layout::{PaneLayout, SplitDirection, SplitSize};
+use zellij_utils::input::layout::{PaneLayout, SplitDirection};
use zellij_utils::input::options::Options;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::{Size, SizeInPixels};
@@ -297,12 +297,15 @@ impl MockScreen {
let _ = self.to_screen.send(ScreenInstruction::NewTab(
default_shell,
Some(pane_layout.clone()),
+ vec![], // floating_panes_layout
tab_name,
self.main_client_id,
));
let _ = self.to_screen.send(ScreenInstruction::ApplyLayout(
pane_layout,
+ vec![], // floating panes layout
pane_ids,
+ vec![], // floating pane ids
plugin_ids,
tab_index,
self.main_client_id,
@@ -323,12 +326,15 @@ impl MockScreen {
let _ = self.to_screen.send(ScreenInstruction::NewTab(
default_shell,
Some(tab_layout.clone()),
+ vec![], // floating_panes_layout
tab_name,
self.main_client_id,
));
let _ = self.to_screen.send(ScreenInstruction::ApplyLayout(
tab_layout,
+ vec![], // floating_panes_layout
pane_ids,
+ vec![], // floating panes ids
plugin_ids,
0,
self.main_client_id,
@@ -472,7 +478,9 @@ fn new_tab(screen: &mut Screen, pid: u32, tab_index: usize) {
screen
.apply_layout(
PaneLayout::default(),
+ vec![], // floating panes layout
new_terminal_ids,
+ vec![], // new floating terminal ids
new_plugin_ids,
tab_index,
client_id,
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap
index 51737530..1ad6b84e 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap
@@ -1,6 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
-assertion_line: 2272
+assertion_line: 2300
expression: "format!(\"{:#?}\", new_tab_action)"
---
Some(
@@ -39,6 +39,7 @@ Some(
external_children_index: None,
},
),
+ [],
None,
0,
1,
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap
index b2f34a98..7190eb8d 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap
@@ -1,6 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
-assertion_line: 2322
+assertion_line: 2350
expression: "format!(\"{:#?}\", new_tab_instruction)"
---
NewTab(
@@ -60,6 +60,7 @@ NewTab(
external_children_index: None,
},
),
+ [],
Some(
"my-awesome-tab-name",
),
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap
index 09a09015..fb7239db 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap
@@ -1,6 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
-assertion_line: 2511
+assertion_line: 2534
expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
---
[
@@ -61,6 +61,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
external_children_index: None,
},
),
+ [],
None,
0,
1,
@@ -207,6 +208,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
external_children_index: None,
},
),
+ [],
None,
1,
1,
diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap
index 11bcb874..bda17001 100644
--- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap
+++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap
@@ -1,6 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
-assertion_line: 2554
+assertion_line: 2577
expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
---
[
@@ -61,6 +61,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
external_children_index: None,
},
),
+ [],
None,
0,
1,
@@ -207,6 +208,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
external_children_index: None,
},
),
+ [],
None,
1,
1,
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index e1a45bf5..7bb13540 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -1,7 +1,7 @@
//! Definition of the actions that can be bound to keys.
use super::command::RunCommandAction;
-use super::layout::{Layout, PaneLayout};
+use super::layout::{FloatingPanesLayout, Layout, PaneLayout};
use crate::cli::CliAction;
use crate::data::InputMode;
use crate::data::{Direction, Resize};
@@ -162,7 +162,7 @@ pub enum Action {
PaneNameInput(Vec<u8>),
UndoRenamePane,
/// Create a new tab, optionally with a specified tab layout.
- NewTab(Option<PaneLayout>, Option<String>), // the String is the tab name
+ NewTab(Option<PaneLayout>, Vec<FloatingPanesLayout>, Option<String>), // the String is the tab name
/// Do nothing.
NoOp,
/// Go to the next tab.
@@ -368,15 +368,24 @@ impl Action {
if tabs.len() > 1 {
return Err(format!("Tab layout cannot itself have tabs"));
} else if !tabs.is_empty() {
- let (tab_name, layout) = tabs.drain(..).next().unwrap();
+ let (tab_name, layout, floating_panes_layout) =
+ tabs.drain(..).next().unwrap();
let name = tab_name.or(name);
- Ok(vec![Action::NewTab(Some(layout), name)])
+ Ok(vec![Action::NewTab(
+ Some(layout),
+ floating_panes_layout,
+ name,
+ )])
} else {
- let layout = layout.new_tab();
- Ok(vec![Action::NewTab(Some(layout), name)])
+ let (layout, floating_panes_layout) = layout.new_tab();
+ Ok(vec![Action::NewTab(
+ Some(layout),
+ floating_panes_layout,
+ name,
+ )])
}
} else {
- Ok(vec![Action::NewTab(None, name)])
+ Ok(vec![Action::NewTab(None, vec![], name)])
}
},
}
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index 6c4a222c..eaa2e395 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -213,9 +213,94 @@ impl fmt::Display for RunPluginLocation {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
pub struct Layout {
- pub tabs: Vec<(Option<String>, PaneLayout)>,
+ pub tabs: Vec<(Option<String>, PaneLayout, Vec<FloatingPanesLayout>)>,
pub focused_tab_index: Option<usize>,
pub template: Option<PaneLayout>,
+ pub floating_panes_template: Vec<FloatingPanesLayout>,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+pub enum PercentOrFixed {
+ Percent(usize), // 1 to 100
+ Fixed(usize), // An absolute number of columns or rows
+}
+
+impl PercentOrFixed {
+ pub fn to_position(&self, whole: usize) -> usize {
+ match self {
+ PercentOrFixed::Percent(percent) => {
+ (whole as f64 / 100.0 * *percent as f64).ceil() as usize
+ },
+ PercentOrFixed::Fixed(fixed) => {
+ if *fixed > whole {
+ whole
+ } else {
+ *fixed
+ }
+ },
+ }
+ }
+}
+
+impl PercentOrFixed {
+ pub fn is_zero(&self) -> bool {
+ match self {
+ PercentOrFixed::Percent(percent) => *percent == 0,
+ PercentOrFixed::Fixed(fixed) => *fixed == 0,
+ }
+ }
+}
+
+impl FromStr for PercentOrFixed {
+ type Err = Box<dyn std::error::Error>;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s.chars().last() == Some('%') {
+ let char_count = s.chars().count();
+ let percent_size = usize::from_str_radix(&s[..char_count.saturating_sub(1)], 10)?;
+ if percent_size <= 100 {
+ Ok(PercentOrFixed::Percent(percent_size))
+ } else {
+ Err("Percent must be between 0 and 100".into())
+ }
+ } else {
+ let fixed_size = usize::from_str_radix(s, 10)?;
+ Ok(PercentOrFixed::Fixed(fixed_size))
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
+pub struct FloatingPanesLayout {
+ // TODO: change name to singular
+ pub name: Option<String>,
+ pub height: Option<PercentOrFixed>,
+ pub width: Option<PercentOrFixed>,
+ pub x: Option<PercentOrFixed>,
+ pub y: Option<PercentOrFixed>,
+ pub run: Option<Run>,
+ pub focus: Option<bool>,
+}
+
+impl FloatingPanesLayout {
+ pub fn add_cwd_to_layout(&mut self, cwd: &PathBuf) {
+ match self.run.as_mut() {
+ Some(run) => run.add_cwd(cwd),
+ None => {
+ self.run = Some(Run::Cwd(cwd.clone()));
+ },
+ }
+ }
+}
+
+impl From<&PaneLayout> for FloatingPanesLayout {
+ fn from(pane_layout: &PaneLayout) -> Self {
+ FloatingPanesLayout {
+ name: pane_layout.name.clone(),
+ run: pane_layout.run.clone(),
+ focus: pane_layout.focus,
+ ..Default::default()
+ }
+ }
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
@@ -276,6 +361,8 @@ impl PaneLayout {
Ok(layouts)
}
pub fn extract_run_instructions(&self) -> Vec<Option<Run>> {
+ // the order of these run instructions is significant and needs to be the same
+ // as the order of the "flattened" layout panes received from eg. position_panes_in_space
let mut run_instructions = vec![];
if self.children.is_empty() {
run_instructions.push(self.run.clone());
@@ -452,11 +539,12 @@ impl Layout {
Ok(String::from_utf8(setup::COMPACT_BAR_LAYOUT.to_vec())?)
}
- pub fn new_tab(&self) -> PaneLayout {
- match &self.template {
+ pub fn new_tab(&self) -> (PaneLayout, Vec<FloatingPanesLayout>) {
+ let template = match &self.template {
Some(template) => template.clone(),
None => PaneLayout::default(),
- }
+ };
+ (template, self.floating_panes_template.clone())
}
pub fn is_empty(&self) -> bool {
@@ -467,7 +555,7 @@ impl Layout {
!self.tabs.is_empty()
}
- pub fn tabs(&self) -> Vec<(Option<String>, PaneLayout)> {
+ pub fn tabs(&self) -> Vec<(Option<String>, PaneLayout, Vec<FloatingPanesLayout>)> {
// String is the tab name
self.tabs.clone()
}
diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs
index f11c8686..d588fcda 100644
--- a/zellij-utils/src/input/unit/layout_test.rs
+++ b/zellij-utils/src/input/unit/layout_test.rs
@@ -90,6 +90,175 @@ fn layout_with_nested_panes() {
}
#[test]
+fn layout_with_floating_panes() {
+ let kdl_layout = r#"
+ layout {
+ floating_panes {
+ pane
+ pane {
+ x 10
+ y "10%"
+ width 10
+ height "10%"
+ }
+ pane x=10 y="10%"
+ pane command="htop"
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
+ let expected_layout = Layout {
+ template: Some(PaneLayout::default()),
+ floating_panes_template: vec![
+ FloatingPanesLayout::default(),
+ FloatingPanesLayout {
+ x: Some(PercentOrFixed::Fixed(10)),
+ y: Some(PercentOrFixed::Percent(10)),
+ width: Some(PercentOrFixed::Fixed(10)),
+ height: Some(PercentOrFixed::Percent(10)),
+ ..Default::default()
+ },
+ FloatingPanesLayout {
+ x: Some(PercentOrFixed::Fixed(10)),
+ y: Some(PercentOrFixed::Percent(10)),
+ ..Default::default()
+ },
+ FloatingPanesLayout {
+ run: Some(Run::Command(RunCommand {
+ command: PathBuf::from("htop"),
+ hold_on_close: true,
+ ..Default::default()
+ })),
+ ..Default::default()
+ },
+ ],
+ ..Default::default()
+ };
+ assert_eq!(layout, expected_layout);
+}
+
+#[test]
+fn layout_with_mixed_panes_and_floating_panes() {
+ let kdl_layout = r#"
+ layout {
+ pane
+ pane
+ floating_panes {
+ pane
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
+ let expected_layout = Layout {
+ template: Some(PaneLayout {
+ children: vec![PaneLayout::default(), PaneLayout::default()],
+ ..Default::default()
+ }),
+ floating_panes_template: vec![FloatingPanesLayout::default()],
+ ..Default::default()
+ };
+ assert_eq!(layout, expected_layout);
+}
+
+#[test]
+fn layout_with_floating_panes_template() {
+ let kdl_layout = r#"
+ layout {
+ pane_template name="my_cool_template" {
+ x 10
+ y "10%"
+ }
+ pane
+ floating_panes {
+ pane
+ my_cool_template
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
+ let expected_layout = Layout {
+ template: Some(PaneLayout {
+ children: vec![PaneLayout::default()],
+ ..Default::default()
+ }),
+ floating_panes_template: vec![
+ FloatingPanesLayout::default(),
+ FloatingPanesLayout {
+ x: Some(PercentOrFixed::Fixed(10)),
+ y: Some(PercentOrFixed::Percent(10)),
+ ..Default::default()
+ },
+ ],
+ ..Default::default()
+ };
+ assert_eq!(layout, expected_layout);
+}
+
+#[test]
+fn layout_with_shared_tiled_and_floating_panes_template() {
+ let kdl_layout = r#"
+ layout {
+ pane_template name="htop" {
+ command "htop"
+ }
+ htop
+ floating_panes {
+ pane
+ htop
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
+ let expected_layout = Layout {
+ template: Some(PaneLayout {
+ children: vec![PaneLayout {
+ run: Some(Run::Command(RunCommand {
+ command: PathBuf::from("htop"),
+ hold_on_close: true,
+ ..Default::default()
+ })),
+ ..Default::default()
+ }],
+ ..Default::default()
+ }),
+ floating_panes_template: vec![
+ FloatingPanesLayout::default(),
+ FloatingPanesLayout {
+ run: Some(Run::Command(RunCommand {
+ command: PathBuf::from("htop"),
+ hold_on_close: true,
+ ..Default::default()
+ })),
+ ..Default::default()
+ },
+ ],
+ ..Default::default()
+ };
+ assert_eq!(layout, expected_layout);
+}
+
+#[test]
+fn layout_with_tabs_and_floating_panes() {
+ let kdl_layout = r#"
+ layout {
+ tab {
+ floating_panes {
+ pane
+ }
+ }
+ tab {
+ floating_panes {
+ pane
+ pane
+ }
+ }
+ }
+ "#;
+ let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
+ assert_snapshot!(format!("{:#?}", layout));
+}
+
+#[test]
fn layout_with_tabs() {
let kdl_layout = r#"
layout {
@@ -98,7 +267,7 @@ fn layout_with_tabs() {
"#;
let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
let expected_layout = Layout {
- tabs: vec![(None, PaneLayout::default())],
+ tabs: vec![(None, PaneLayout::default(), vec![])],
template: Some(PaneLayout::default()),
..Default::default()
};
@@ -134,6 +303,7 @@ fn layout_with_nested_differing_tabs() {
],
..Default::default()
},
+ vec![], // floating panes
),
(
None,
@@ -142,6 +312,7 @@ fn layout_with_nested_differing_tabs() {
children: vec![PaneLayout::default(), PaneLayout::default()],
..Default::default()
},
+ vec![], // floating panes
),
],
template: Some(PaneLayout::default()),
@@ -412,6 +583,7 @@ fn layout_with_tab_names() {
children: vec![],
..Default::default()
},
+ vec![], // floating panes
),
(
Some("my cool tab name 2".into()),
@@ -419,6 +591,7 @@ fn layout_with_tab_names() {
children: vec![],
..Default::default()
},
+ vec![], // floating panes
),
],
template: Some(PaneLayout::default()),
@@ -439,9 +612,9 @@ fn layout_with_focused_tab() {
let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
let expected_layout = Layout {
tabs: vec![
- (None, PaneLayout::default()),
- (None, PaneLayout::default()),
- (None, PaneLayout::default()),
+ (None, PaneLayout::default(), vec![]),
+ (None, PaneLayout::default(), vec![]),
+ (None, PaneLayout::default(), vec![]),
],
template: Some(PaneLayout::default()),
focused_tab_index: Some(1),
@@ -488,6 +661,7 @@ fn layout_with_tab_templates() {
],
..Default::default()
},
+ vec![], // floating panes
),
(
Some("my second tab".into()),
@@ -504,6 +678,7 @@ fn layout_with_tab_templates() {
],
..Default::default()
},
+ vec![], // floating panes
),
(
None,
@@ -516,6 +691,7 @@ fn layout_with_tab_templates() {
],
..Default::default()
},
+ vec![], // floating panes
),
],
template: Some(PaneLayout::default()),
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap
index 8b6d9050..0fc51aad 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1081
+assertion_line: 1101
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -62,4 +62,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap
index 4b026d75..64d47573 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1046
+assertion_line: 1066
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -65,4 +65,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap
index f7f904f6..36eeb717 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 711
+assertion_line: 744
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -138,4 +138,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap
index 003a3d41..ac027457 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 801
+assertion_line: 696
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -82,6 +82,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
(
None,
@@ -129,6 +130,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
],
focused_tab_index: None,
@@ -144,4 +146,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap
index d23bb40c..0386fd19 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1098
+assertion_line: 1118
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -59,4 +59,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap
index abce2964..487bfb82 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1064
+assertion_line: 1084
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -59,4 +59,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap
index a964c655..0c4cb680 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 899
+assertion_line: 796
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -105,6 +105,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
(
None,
@@ -183,6 +184,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
],
focused_tab_index: None,
@@ -198,4 +200,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap
index 48bb1183..beb1f67f 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1133
+assertion_line: 1153
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -61,4 +61,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap
index 7d57e6d0..36cc0c24 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1116
+assertion_line: 1136
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -63,4 +63,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap
index 58a78678..71b46db4 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1516
+assertion_line: 1536
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -58,6 +58,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
],
focused_tab_index: None,
@@ -73,4 +74,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap
index c6b3ed5f..5c39bbd1 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1537
+assertion_line: 1557
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -102,6 +102,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
],
focused_tab_index: None,
@@ -117,4 +118,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap
index cd1a51e1..4c6bfd2d 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1556
+assertion_line: 1576
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -87,6 +87,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
],
focused_tab_index: None,
@@ -102,4 +103,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap
index ba246e06..5256565c 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1403
+assertion_line: 1423
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -55,4 +55,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap
index 3b546891..b0eaf353 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1435
+assertion_line: 1455
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -55,4 +55,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap
index 50b92892..5aa11997 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1455
+assertion_line: 1475
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -55,4 +55,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap
index f5d6b95a..8c5d71c1 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1416
+assertion_line: 1436
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -55,4 +55,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap
index 09d209c1..b54634fb 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1471
+assertion_line: 1491
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -58,6 +58,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
],
focused_tab_index: None,
@@ -73,4 +74,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap
index 32d1341c..c16d495e 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 281
+assertion_line: 283
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -39,4 +39,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap
index 136c95fb..6723bc21 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 294
+assertion_line: 296
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -39,4 +39,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap
index f61145b4..8065fc4d 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 549
+assertion_line: 555
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -71,6 +71,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
(
Some(
@@ -138,6 +139,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
(
None,
@@ -182,6 +184,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
],
focused_tab_index: None,
@@ -228,4 +231,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap
index 77b7478c..687d7618 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 756
+assertion_line: 650
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -122,4 +122,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap
index fd12e42b..e5a655f7 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 729
+assertion_line: 624
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -91,4 +91,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap
index 561c35f1..cbbddf74 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 626
+assertion_line: 582
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -236,4 +236,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap
index fbe11152..464c1647 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 583
+assertion_line: 603
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -80,6 +80,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
],
focused_tab_index: None,
@@ -95,4 +96,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap
new file mode 100644
index 00000000..5cdf517a
--- /dev/null
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap
@@ -0,0 +1,102 @@
+---
+source: zellij-utils/src/input/./unit/layout_test.rs
+assertion_line: 263
+expression: "format!(\"{:#?}\", layout)"
+---
+Layout {
+ tabs: [
+ (
+ None,
+ PaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [
+ PaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [],
+ split_size: None,
+ run: None,
+ borderless: false,
+ focus: None,
+ external_children_index: None,
+ },
+ ],
+ split_size: None,
+ run: None,
+ borderless: false,
+ focus: None,
+ external_children_index: None,
+ },
+ [
+ FloatingPanesLayout {
+ name: None,
+ height: None,
+ width: None,
+ x: None,
+ y: None,
+ run: None,
+ focus: None,
+ },
+ ],
+ ),
+ (
+ None,
+ PaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [
+ PaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [],
+ split_size: None,
+ run: None,
+ borderless: false,
+ focus: None,
+ external_children_index: None,
+ },
+ ],
+ split_size: None,
+ run: None,
+ borderless: false,
+ focus: None,
+ external_children_index: None,
+ },
+ [
+ FloatingPanesLayout {
+ name: None,
+ height: None,
+ width: None,
+ x: None,
+ y: None,
+ run: None,
+ focus: None,
+ },
+ FloatingPanesLayout {
+ name: None,
+ height: None,
+ width: None,
+ x: None,
+ y: None,
+ run: None,
+ focus: None,
+ },
+ ],
+ ),
+ ],
+ focused_tab_index: None,
+ template: Some(
+ PaneLayout {
+ children_split_direction: Horizontal,
+ name: None,
+ children: [],
+ split_size: None,
+ run: None,
+ borderless: false,
+ focus: None,
+ external_children_index: None,
+ },
+ ),
+ floating_panes_template: [],
+}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap
index 94a52aac..4f4552ee 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1287
+assertion_line: 1307
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -41,4 +41,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap
index 6f2255cd..fc5523fa 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1233
+assertion_line: 1253
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -41,4 +41,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap
index d659eba3..3c00d46f 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1250
+assertion_line: 1270
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -41,4 +41,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap
index feb8a88b..b88f2a9e 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1268
+assertion_line: 1288
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -41,4 +41,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap
index 6cad38df..3d561463 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1305
+assertion_line: 1325
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -41,4 +41,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap
index 0f488990..89332ea3 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1235
+assertion_line: 1343
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -33,4 +33,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap
index 42fb84d2..3797afc5 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1357
+assertion_line: 1377
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -41,4 +41,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap
index a09f2ab2..072d0bc5 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1339
+assertion_line: 1359
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -41,4 +41,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap
index 32bc6880..a6aac1f1 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1298
+assertion_line: 1393
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -34,4 +34,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap
index f5f50be1..e77e7b95 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1314
+assertion_line: 1409
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -34,4 +34,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap
index 4508cb07..004238aa 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1486
+assertion_line: 1506
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -58,6 +58,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
],
focused_tab_index: None,
@@ -73,4 +74,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap
index ac1d4240..573e0465 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1501
+assertion_line: 1521
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -58,6 +58,7 @@ Layout {
focus: None,
external_children_index: None,
},
+ [],
),
],
focused_tab_index: None,
@@ -73,4 +74,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs
index c5a84a7b..a24e6948 100644
--- a/zellij-utils/src/kdl/kdl_layout_parser.rs
+++ b/zellij-utils/src/kdl/kdl_layout_parser.rs
@@ -1,7 +1,10 @@
use crate::input::{
command::RunCommand,
config::ConfigError,
- layout::{Layout, PaneLayout, Run, RunPlugin, RunPluginLocation, SplitDirection, SplitSize},
+ layout::{
+ FloatingPanesLayout, Layout, PaneLayout, PercentOrFixed, Run, RunPlugin, RunPluginLocation,
+ SplitDirection, SplitSize,
+ },
};
use kdl::*;
@@ -23,12 +26,19 @@ use std::path::PathBuf;
use std::vec::Vec;
use url::Url;
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum PaneOrFloatingPane {
+ Pane(PaneLayout),
+ FloatingPane(FloatingPanesLayout),
+ Either(PaneLayout),
+}
+
pub struct KdlLayoutParser<'a> {
global_cwd: Option<PathBuf>,
raw_layout: &'a str,
- tab_templates: HashMap<String, (PaneLayout, KdlNode)>,
- pane_templates: HashMap<String, (PaneLayout, KdlNode)>,
- default_tab_template: Option<(PaneLayout, KdlNode)>,
+ tab_templates: HashMap<String, (PaneLayout, Vec<FloatingPanesLayout>, KdlNode)>,
+ pane_templates: HashMap<String, (PaneOrFloatingPane, KdlNode)>,
+ default_tab_template: Option<(PaneLayout, Vec<FloatingPanesLayout>, KdlNode)>,
}
impl<'a> KdlLayoutParser<'a> {
@@ -78,11 +88,28 @@ impl<'a> KdlLayoutParser<'a> {
|| property_name == "pane"
|| property_name == "children"
}
+ fn is_a_valid_floating_pane_property(&self, property_name: &str) -> bool {
+ property_name == "borderless"
+ || property_name == "focus"
+ || property_name == "name"
+ || property_name == "plugin"
+ || property_name == "command"
+ || property_name == "edit"
+ || property_name == "cwd"
+ || property_name == "args"
+ || property_name == "close_on_exit"
+ || property_name == "start_suspended"
+ || property_name == "x"
+ || property_name == "y"
+ || property_name == "width"
+ || property_name == "height"
+ }
fn is_a_valid_tab_property(&self, property_name: &str) -> bool {
property_name == "focus"
|| property_name == "name"
|| property_name == "split_direction"
|| property_name == "cwd"
+ || property_name == "floating_panes"
}
fn assert_legal_node_name(&self, name: &str, kdl_node: &KdlNode) -> Result<(), ConfigError> {
if name.contains(char::is_whitespace) {
@@ -168,6 +195,60 @@ impl<'a> KdlLayoutParser<'a> {
Ok(None)
}
}
+ fn parse_percent_or_fixed(
+ &self,
+ kdl_node: &KdlNode,
+ value_name: &str,
+ can_be_zero: bool,
+ ) -> Result<Option<PercentOrFixed>, ConfigError> {
+ if let Some(size) = kdl_get_string_property_or_child_value!(kdl_node, value_name) {
+ match PercentOrFixed::from_str(size) {
+ Ok(size) => {
+ if !can_be_zero && size.is_zero() {
+ Err(kdl_parsing_error!(
+ format!("{} should be greater than 0", value_name),
+ kdl_node
+ ))
+ } else {
+ Ok(Some(size))
+ }
+ },
+ Err(_e) => Err(kdl_parsing_error!(
+ format!(
+ "{} should be a fixed number (eg. 1) or a quoted percent (eg. \"50%\")",
+ value_name
+ ),
+ kdl_node
+ )),
+ }
+ } else if let Some(size) = kdl_get_int_property_or_child_value!(kdl_node, value_name) {
+ if size == 0 && !can_be_zero {
+ return Err(kdl_parsing_error!(
+ format!("{} should be greater than 0", value_name),
+ kdl_node
+ ));
+ }
+ Ok(Some(PercentOrFixed::Fixed(size as usize)))
+ } else if let Some(node) = kdl_property_or_child_value_node!(kdl_node, "size") {
+ Err(kdl_parsing_error!(
+ format!(
+ "{} should be a fixed number (eg. 1) or a quoted percent (eg. \"50%\")",
+ value_name
+ ),
+ node
+ ))
+ } else if let Some(node) = kdl_child_with_name!(kdl_node, "size") {
+ Err(kdl_parsing_error!(
+ format!(
+ "{} cannot be bare, it should have a value (eg. 'size 1', or 'size \"50%\"')",
+ value_name
+ ),
+ node
+ ))
+ } else {
+ Ok(None)
+ }
+ }
fn parse_plugin_block(&self, plugin_block: &KdlNode) -> Result<Option<Run>, ConfigError> {
let _allow_exec_host_cmd =
kdl_get_bool_property_or_child_value_with_error!(plugin_block, "_allow_exec_host_cmd")
@@ -347,6 +428,31 @@ impl<'a> KdlLayoutParser<'a> {
..Default::default()
})
}
+ fn parse_floating_pane_node(
+ &self,
+ kdl_node: &KdlNode,
+ ) -> Result<FloatingPanesLayout, ConfigError> {
+ self.assert_valid_floating_pane_properties(kdl_node)?;
+ let height = self.parse_percent_or_fixed(kdl_node, "height", false)?;
+ let width = self.parse_percent_or_fixed(kdl_node, "width", false)?;
+ let x = self.parse_percent_or_fixed(kdl_node, "x", true)?;
+ let y = self.parse_percent_or_fixed(kdl_node, "y", true)?;
+ let run = self.parse_command_plugin_or_edit_block(kdl_node)?;
+ let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus");
+ let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name")
+ .map(|name| name.to_string());
+ self.assert_no_mixed_children_and_properties(kdl_node)?;
+ Ok(FloatingPanesLayout {
+ name,
+ height,
+ width,
+ x,
+ y,
+ run,
+ focus,
+ ..Default::default()
+ })
+ }
fn insert_children_to_pane_template(
&self,
kdl_node: &KdlNode,
@@ -377,60 +483,201 @@ impl<'a> KdlLayoutParser<'a> {
fn parse_pane_node_with_template(
&self,
kdl_node: &KdlNode,
- mut pane_template: PaneLayout,
+ pane_template: PaneOrFloatingPane,
pane_template_kdl_node: &KdlNode,
) -> Result<PaneLayout, ConfigError> {
- let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless");
- let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus");
- let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name")
- .map(|name| name.to_string());
- let args = self.parse_args(kdl_node)?;
- let close_on_exit =
- kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit");
- let start_suspended =
- kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended");
- let split_size = self.parse_split_size(kdl_node)?;
- let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?;
- self.assert_no_bare_attributes_in_pane_node_with_template(
- &run,
- &pane_template.run,
- &args,
- &close_on_exit,
- &start_suspended,
- kdl_node,
- )?;
- self.insert_children_to_pane_template(
- kdl_node,
- &mut pane_template,
- pane_template_kdl_node,
- )?;
- pane_template.run = Run::merge(&pane_template.run, &run);
- if let Some(pane_template_run_command) = pane_template.run.as_mut() {
- // we need to do this because panes consuming a pane_template
- // can have bare args without a command
- pane_template_run_command.add_args(args);
- pane_template_run_command.add_close_on_exit(close_on_exit);
- pane_template_run_command.add_start_suspended(start_suspended);
- };
- if let Some(borderless) = borderless {
- pane_template.borderless = borderless;
- }
- if let Some(focus) = focus {
- pane_template.focus = Some(focus);
- }
- if let Some(name) = name {
- pane_template.name = Some(name);
- }
- if let Some(split_size) = split_size {
- pane_template.split_size = Some(split_size);
+ match pane_template {
+ PaneOrFloatingPane::Pane(mut pane_template)
+ | PaneOrFloatingPane::Either(mut pane_template) => {
+ let borderless =
+ kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless");
+ let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus");
+ let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name")
+ .map(|name| name.to_string());
+ let args = self.parse_args(kdl_node)?;
+ let close_on_exit =
+ kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit");
+ let start_suspended =
+ kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended");
+ let split_size = self.parse_split_size(kdl_node)?;
+ let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?;
+ self.assert_no_bare_attributes_in_pane_node_with_template(
+ &run,
+ &pane_template.run,
+ &args,
+ &close_on_exit,
+ &start_suspended,
+ kdl_node,
+ )?;
+ self.insert_children_to_pane_template(
+ kdl_node,
+ &mut pane_template,
+ pane_template_kdl_node,
+ )?;
+ pane_template.run = Run::merge(&pane_template.run, &run);
+ if let Some(pane_template_run_command) = pane_template.run.as_mut() {
+ // we need to do this because panes consuming a pane_template
+ // can have bare args without a command
+ pane_template_run_command.add_args(args);
+ pane_template_run_command.add_close_on_exit(close_on_exit);
+ pane_template_run_command.add_start_suspended(start_suspended);
+ };
+ if let Some(borderless) = borderless {
+ pane_template.borderless = borderless;
+ }
+ if let Some(focus) = focus {
+ pane_template.focus = Some(focus);
+ }
+ if let Some(name) = name {
+ pane_template.name = Some(name);
+ }
+ if let Some(split_size) = split_size {
+ pane_template.split_size = Some(split_size);
+ }
+ if let Some(index_of_children) = pane_template.external_children_index {
+ pane_template
+ .children
+ .insert(index_of_children, PaneLayout::default());
+ }
+ pane_template.external_children_index = None;
+ Ok(pane_template)
+ },
+ PaneOrFloatingPane::FloatingPane(_) => {
+ let pane_template_name = kdl_get_string_property_or_child_value_with_error!(
+ pane_template_kdl_node,
+ "name"
+ )
+ .map(|name| name.to_string());
+ Err(ConfigError::new_layout_kdl_error(
+ format!("pane_template {}, is a floating pane template (derived from its properties) and cannot be applied to a tiled pane", pane_template_name.unwrap_or("".into())),
+ kdl_node.span().offset(),
+ kdl_node.span().len(),
+ ))
+ },
}
- if let Some(index_of_children) = pane_template.external_children_index {
- pane_template
- .children
- .insert(index_of_children, PaneLayout::default());
+ }
+ fn parse_floating_pane_node_with_template(
+ &self,
+ kdl_node: &KdlNode,
+ pane_template: PaneOrFloatingPane,
+ pane_template_kdl_node: &KdlNode,
+ ) -> Result<FloatingPanesLayout, ConfigError> {
+ match pane_template {
+ PaneOrFloatingPane::Pane(_) => {
+ let pane_template_name = kdl_get_string_property_or_child_value_with_error!(
+ pane_template_kdl_node,
+ "name"
+ )
+ .map(|name| name.to_string());
+ Err(ConfigError::new_layout_kdl_error(
+ format!("pane_template {}, is a non-floating pane template (derived from its properties) and cannot be applied to a floating pane", pane_template_name.unwrap_or("".into())),
+ kdl_node.span().offset(),
+ kdl_node.span().len(),
+ ))
+ },
+ PaneOrFloatingPane::FloatingPane(mut pane_template) => {
+ let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus");
+ let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name")
+ .map(|name| name.to_string());
+ let args = self.parse_args(kdl_node)?;
+ let close_on_exit =
+ kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit");
+ let start_suspended =
+ kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended");
+ let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?;
+ self.assert_no_bare_attributes_in_pane_node_with_template(
+ &run,
+ &pane_template.run,
+ &args,
+ &close_on_exit,
+ &start_suspended,
+ kdl_node,
+ )?;
+ pane_template.run = Run::merge(&pane_template.run, &run);
+ if let Some(pane_template_run_command) = pane_template.run.as_mut() {
+ // we need to do this because panes consuming a pane_template
+ // can have bare args without a command
+ pane_template_run_command.add_args(args);
+ pane_template_run_command.add_close_on_exit(close_on_exit);
+ pane_template_run_command.add_start_suspended(start_suspended);
+ };
+ if let Some(focus) = focus {
+ pane_template.focus = Some(focus);
+ }
+ if let Some(name) = name {
+ pane_template.name = Some(name);
+ }
+ let height = self.parse_percent_or_fixed(kdl_node, "height", false)?;
+ let width = self.parse_percent_or_fixed(kdl_node, "width", false)?;
+ let x = self.parse_percent_or_fixed(kdl_node, "x", true)?;
+ let y = self.parse_percent_or_fixed(kdl_node, "y", true)?;
+ // let mut floating_pane = FloatingPanesLayout::from(&pane_template);
+ if let Some(height) = height {
+ pane_template.height = Some(height);
+ }
+ if let Some(width) = width {
+ pane_template.width = Some(width);
+ }
+ if let Some(y) = y {
+ pane_template.y = Some(y);
+ }
+ if let Some(x) = x {
+ pane_template.x = Some(x);
+ }
+ Ok(pane_template)
+ },
+ PaneOrFloatingPane::Either(mut pane_template) => {
+ let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus");
+ let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name")
+ .map(|name| name.to_string());
+ let args = self.parse_args(kdl_node)?;
+ let close_on_exit =
+ kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit");
+ let start_suspended =
+ kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended");
+ let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?;
+ self.assert_no_bare_attributes_in_pane_node_with_template(
+ &run,
+ &pane_template.run,
+ &args,
+ &close_on_exit,
+ &start_suspended,
+ kdl_node,
+ )?;
+ pane_template.run = Run::merge(&pane_template.run, &run);
+ if let Some(pane_template_run_command) = pane_template.run.as_mut() {
+ // we need to do this because panes consuming a pane_template
+ // can have bare args without a command
+ pane_template_run_command.add_args(args);
+ pane_template_run_command.add_close_on_exit(close_on_exit);
+ pane_template_run_command.add_start_suspended(start_suspended);
+ };
+ if let Some(focus) = focus {
+ pane_template.focus = Some(focus);
+ }
+ if let Some(name) = name {
+ pane_template.name = Some(name);
+ }
+ let height = self.parse_percent_or_fixed(kdl_node, "height", false)?;
+ let width = self.parse_percent_or_fixed(kdl_node, "width", false)?;
+ let x = self.parse_percent_or_fixed(kdl_node, "x", true)?;
+ let y = self.parse_percent_or_fixed(kdl_node, "y", true)?;
+ let mut floating_pane = FloatingPanesLayout::from(&pane_template);
+ if let Some(height) = height {
+ floating_pane.height = Some(height);
+ }
+ if let Some(width) = width {
+ floating_pane.width = Some(width);
+ }
+ if let Some(y) = y {
+ floating_pane.y = Some(y);
+ }
+ if let Some(x) = x {
+ floating_pane.x = Some(x);
+ }
+ Ok(floating_pane)
+ },
}
- pane_template.external_children_index = None;
- Ok(pane_template)
}
fn parse_split_direction(&self, kdl_node: &KdlNode) -> Result<SplitDirection, ConfigError> {
match kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction") {
@@ -447,8 +694,104 @@ impl<'a> KdlLayoutParser<'a> {
None => Ok(SplitDirection::default()),
}
}
+ fn has_only_neutral_pane_template_properties(
+ &self,
+ kdl_node: &KdlNode,
+ ) -> Result<bool, ConfigError> {
+ // pane properties
+ let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless");
+ let split_size = self.parse_split_size(kdl_node)?;
+ let split_direction =
+ kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction");
+ let has_children_nodes = self.has_child_nodes(kdl_node);
+
+ // floating pane properties
+ let height = self.parse_percent_or_fixed(kdl_node, "height", false)?;
+ let width = self.parse_percent_or_fixed(kdl_node, "width", false)?;
+ let x = self.parse_percent_or_fixed(kdl_node, "x", true)?;
+ let y = self.parse_percent_or_fixed(kdl_node, "y", true)?;
+
+ let has_pane_properties = borderless.is_some()
+ || split_size.is_some()
+ || split_direction.is_some()
+ || has_children_nodes;
+ let has_floating_pane_properties =
+ height.is_some() || width.is_some() || x.is_some() || y.is_some();
+ if has_pane_properties || has_floating_pane_properties {
+ Ok(false)
+ } else {
+ Ok(true)
+ }
+ }
+ fn differentiate_pane_and_floating_pane_template(
+ &self,
+ kdl_node: &KdlNode,
+ ) -> Result<bool, ConfigError> {
+ // returns true if it's a floating_pane template, false if not
+
+ // pane properties
+ let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless");
+ let split_size = self.parse_split_size(kdl_node)?;
+ let split_direction =
+ kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction");
+ let has_children_nodes = self.has_child_nodes(kdl_node);
+
+ // floating pane properties
+ let height = self.parse_percent_or_fixed(kdl_node, "height", false)?;
+ let width = self.parse_percent_or_fixed(kdl_node, "width", false)?;
+ let x = self.parse_percent_or_fixed(kdl_node, "x", true)?;
+ let y = self.parse_percent_or_fixed(kdl_node, "y", true)?;
+
+ let has_pane_properties = borderless.is_some()
+ || split_size.is_some()
+ || split_direction.is_some()
+ || has_children_nodes;
+ let has_floating_pane_properties =
+ height.is_some() || width.is_some() || x.is_some() || y.is_some();
+
+ if has_pane_properties && has_floating_pane_properties {
+ let mut pane_properties = vec![];
+ if borderless.is_some() {
+ pane_properties.push("borderless");
+ }
+ if split_size.is_some() {
+ pane_properties.push("split_size");
+ }
+ if split_direction.is_some() {
+ pane_properties.push("split_direction");
+ }
+ if has_children_nodes {
+ pane_properties.push("child nodes");
+ }
+ let mut floating_pane_properties = vec![];
+ if height.is_some() {
+ floating_pane_properties.push("height");
+ }
+ if width.is_some() {
+ floating_pane_properties.push("width");
+ }
+ if x.is_some() {
+ floating_pane_properties.push("x");
+ }
+ if y.is_some() {
+ floating_pane_properties.push("y");
+ }
+ Err(ConfigError::new_layout_kdl_error(
+ format!(
+ "A pane_template cannot have both pane ({}) and floating pane ({}) properties",
+ pane_properties.join(", "),
+ floating_pane_properties.join(", ")
+ ),
+ kdl_node.span().offset(),
+ kdl_node.span().len(),
+ ))
+ } else if has_floating_pane_properties {
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
fn parse_pane_template_node(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> {
- self.assert_valid_pane_properties(kdl_node)?;
let template_name = kdl_get_string_property_or_child_value!(kdl_node, "name")
.map(|s| s.to_string())
.ok_or(ConfigError::new_layout_kdl_error(
@@ -458,39 +801,84 @@ impl<'a> KdlLayoutParser<'a> {
))?;
self.assert_legal_node_name(&template_name, kdl_node)?;
self.assert_legal_template_name(&template_name, kdl_node)?;
- let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless");
let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus");
- let split_size = self.parse_split_size(kdl_node)?;
let run = self.parse_command_plugin_or_edit_block(kdl_node)?;
- let children_split_direction = self.parse_split_direction(kdl_node)?;
- let (external_children_index, pane_parts) = match kdl_children_nodes!(kdl_node) {
- Some(children) => self.parse_child_pane_nodes_for_pane(&children)?,
- None => (None, vec![]),
- };
- self.assert_no_mixed_children_and_properties(kdl_node)?;
- self.pane_templates.insert(
- template_name,
- (
- PaneLayout {
- borderless: borderless.unwrap_or_default(),
- focus,
- split_size,
- run,
- children_split_direction,
- external_children_index,
- children: pane_parts,
- ..Default::default()
- },
- kdl_node.clone(),
- ),
- );
+
+ let is_floating = self.differentiate_pane_and_floating_pane_template(&kdl_node)?;
+ let can_be_either_floating_or_tiled =
+ self.has_only_neutral_pane_template_properties(&kdl_node)?;
+ if can_be_either_floating_or_tiled {
+ self.assert_valid_pane_or_floating_pane_properties(kdl_node)?;
+ self.pane_templates.insert(
+ template_name,
+ (
+ PaneOrFloatingPane::Either(PaneLayout {
+ focus,
+ run,
+ ..Default::default()
+ }),
+ kdl_node.clone(),
+ ),
+ );
+ } else if is_floating {
+ self.assert_valid_floating_pane_properties(kdl_node)?;
+ // floating pane properties
+ let height = self.parse_percent_or_fixed(kdl_node, "height", false)?;
+ let width = self.parse_percent_or_fixed(kdl_node, "width", false)?;
+ let x = self.parse_percent_or_fixed(kdl_node, "x", true)?;
+ let y = self.parse_percent_or_fixed(kdl_node, "y", true)?;
+ self.pane_templates.insert(
+ template_name,
+ (
+ PaneOrFloatingPane::FloatingPane(FloatingPanesLayout {
+ focus,
+ run,
+ height,
+ width,
+ x,
+ y,
+ ..Default::default()
+ }),
+ kdl_node.clone(),
+ ),
+ );
+ } else {
+ self.assert_valid_pane_properties(kdl_node)?;
+ // pane properties
+ let borderless =
+ kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless");
+ let split_size = self.parse_split_size(kdl_node)?;
+ let children_split_direction = self.parse_split_direction(kdl_node)?;
+ let (external_children_index, pane_parts) = match kdl_children_nodes!(kdl_node) {
+ Some(children) => self.parse_child_pane_nodes_for_pane(&children)?,
+ None => (None, vec![]),
+ };
+ self.assert_no_mixed_children_and_properties(kdl_node)?;
+ self.pane_templates.insert(
+ template_name,
+ (
+ PaneOrFloatingPane::Pane(PaneLayout {
+ borderless: borderless.unwrap_or_default(),
+ focus,
+ split_size,
+ run,
+ children_split_direction,
+ external_children_index,
+ children: pane_parts,
+ ..Default::default()
+ }),
+ kdl_node.clone(),
+ ),
+ );
+ }
+
Ok(())
}
fn parse_tab_node(
&mut self,
kdl_node: &KdlNode,
- ) -> Result<(bool, Option<String>, PaneLayout), ConfigError> {
- // (is_focused, Option<tab_name>, PaneLayout)
+ ) -> Result<(bool, Option<String>, PaneLayout, Vec<FloatingPanesLayout>), ConfigError> {
+ // (is_focused, Option<tab_name>, PaneLayout, Vec<FloatingPanesLayout>)
self.assert_valid_tab_properties(kdl_node)?;
let tab_name =
kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string());
@@ -498,8 +886,11 @@ impl<'a> KdlLayoutParser<'a> {
kdl_get_string_property_or_child_value!(kdl_node, "cwd").map(|c| PathBuf::from(c));
let is_focused = kdl_get_bool_property_or_child_value!(kdl_node, "focus").unwrap_or(false);
let children_split_direction = self.parse_split_direction(kdl_node)?;
+ let mut child_floating_panes = vec![];
let children = match kdl_children_nodes!(kdl_node) {
- Some(children) => self.parse_child_pane_nodes_for_tab(children)?,
+ Some(children) => {
+ self.parse_child_pane_nodes_for_tab(children, &mut child_floating_panes)?
+ },
None => vec![],
};
let mut pane_layout = PaneLayout {
@@ -510,11 +901,12 @@ impl<'a> KdlLayoutParser<'a> {
if let Some(cwd_prefix) = &self.cwd_prefix(tab_cwd.as_ref())? {
pane_layout.add_cwd_to_layout(&cwd_prefix);
}
- Ok((is_focused, tab_name, pane_layout))
+ Ok((is_focused, tab_name, pane_layout, child_floating_panes))
}
fn parse_child_pane_nodes_for_tab(
&self,
children: &[KdlNode],
+ child_floating_panes: &mut Vec<FloatingPanesLayout>,
) -> Result<Vec<PaneLayout>, ConfigError> {
let mut nodes = vec![];
for child in children {
@@ -528,6 +920,8 @@ impl<'a> KdlLayoutParser<'a> {
pane_template,
&pane_template_kdl_node,
)?);
+ } else if kdl_name!(child) == "floating_panes" {
+ self.populate_floating_pane_children(child, child_floating_panes)?;
} else if self.is_a_valid_tab_property(kdl_name!(child)) {
return Err(ConfigError::new_layout_kdl_error(
format!("Tab property '{}' must be placed on the tab title line and not in the child braces", kdl_name!(child)),
@@ -586,6 +980,18 @@ impl<'a> KdlLayoutParser<'a> {
}
Ok((external_children_index, nodes))
}
+ fn has_child_nodes(&self, kdl_node: &KdlNode) -> bool {
+ if let Some(children) = kdl_children_nodes!(kdl_node) {
+ for child in children {
+ if kdl_name!(child) == "pane" || kdl_name!(child) == "children" {
+ return true;
+ } else if self.pane_templates.get(kdl_name!(child)).is_some() {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
fn has_child_panes_tabs_or_templates(&self, kdl_node: &KdlNode) -> bool {
if let Some(children) = kdl_children_nodes!(kdl_node) {
for child in children {
@@ -705,6 +1111,68 @@ impl<'a> KdlLayoutParser<'a> {
}
Ok(())
}
+ fn assert_valid_floating_pane_properties(
+ &self,
+ pane_node: &KdlNode,
+ ) -> Result<(), ConfigError> {
+ for entry in pane_node.entries() {
+ match entry
+ .name()
+ .map(|e| e.value())
+ .or_else(|| entry.value().as_string())
+ {
+ Some(string_name) => {
+ if !self.is_a_valid_floating_pane_property(string_name) {
+ return Err(ConfigError::new_layout_kdl_error(
+ format!("Unknown floating pane property: {}", string_name),
+ entry.span().offset(),
+ entry.span().len(),
+ ));
+ }
+ },
+ None => {
+ return Err(ConfigError::new_layout_kdl_error(
+ "Unknown floating pane property".into(),
+ entry.span().offset(),
+ entry.span().len(),
+ ));
+ },
+ }
+ }
+ Ok(())
+ }
+ fn assert_valid_pane_or_floating_pane_properties(
+ &self,
+ pane_node: &KdlNode,
+ ) -> Result<(), ConfigError> {
+ for entry in pane_node.entries() {
+ match entry
+ .name()
+ .map(|e| e.value())
+ .or_else(|| entry.value().as_string())
+ {
+ Some(string_name) => {
+ if !self.is_a_valid_floating_pane_property(string_name)
+ || !self.is_a_valid_pane_property(string_name)
+ {
+ return Err(ConfigError::new_layout_kdl_error(
+ format!("Unknown pane property: {}", string_name),
+ entry.span().offset(),
+ entry.span().len(),
+ ));
+ }
+ },
+ None => {
+ return Err(ConfigError::new_layout_kdl_error(
+ "Unknown pane property".into(),
+ entry.span().offset(),
+ entry.span().len(),
+ ));
+ },
+ }
+ }
+ Ok(())
+ }
fn assert_valid_tab_properties(&self, pane_node: &KdlNode) -> Result<(), ConfigError> {
let all_property_names = kdl_property_names!(pane_node);
for name in all_property_names {
@@ -785,9 +1253,10 @@ impl<'a> KdlLayoutParser<'a> {
&self,
kdl_node: &KdlNode,
mut tab_layout: PaneLayout,
+ mut tab_template_floating_panes: Vec<FloatingPanesLayout>,
tab_layout_kdl_node: &KdlNode,
- ) -> Result<(bool, Option<String>, PaneLayout), ConfigError> {
- // (is_focused, Option<tab_name>, PaneLayout)
+ ) -> Result<(bool, Option<String>, PaneLayout, Vec<FloatingPanesLayout>), ConfigError> {
+ // (is_focused, Option<tab_name>, PaneLayout, Vec<FloatingPanesLayout>)
let tab_name =
kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string());
let tab_cwd =
@@ -796,7 +1265,8 @@ impl<'a> KdlLayoutParser<'a> {
let children_split_direction = self.parse_split_direction(kdl_node)?;
match kdl_children_nodes!(kdl_node) {
Some(children) => {
- let child_panes = self.parse_child_pane_nodes_for_tab(children)?;
+ let child_panes = self
+ .parse_child_pane_nodes_for_tab(children, &mut tab_template_floating_panes)?;
let child_panes_layout = PaneLayout {
children_split_direction,
children: child_panes,
@@ -821,7 +1291,12 @@ impl<'a> KdlLayoutParser<'a> {
tab_layout.add_cwd_to_layout(&cwd_prefix);
}
tab_layout.external_children_index = None;
- Ok((is_focused, tab_name, tab_layout))
+ Ok((
+ is_focused,
+ tab_name,
+ tab_layout,
+ tab_template_floating_panes,
+ ))
}
fn populate_one_tab_template(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> {
let template_name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name")
@@ -850,22 +1325,29 @@ impl<'a> KdlLayoutParser<'a> {
kdl_node.span().len(),
));
}
+ let (tab_template, tab_template_floating_panes) = self.parse_tab_template_node(kdl_node)?;
self.tab_templates.insert(
template_name,
- (self.parse_tab_template_node(kdl_node)?, kdl_node.clone()),
+ (tab_template, tab_template_floating_panes, kdl_node.clone()),
);
Ok(())
}
fn populate_default_tab_template(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> {
+ let (tab_template, tab_template_floating_panes) = self.parse_tab_template_node(kdl_node)?;
self.default_tab_template =
- Some((self.parse_tab_template_node(kdl_node)?, kdl_node.clone()));
+ Some((tab_template, tab_template_floating_panes, kdl_node.clone()));
Ok(())
}
- fn parse_tab_template_node(&self, kdl_node: &KdlNode) -> Result<PaneLayout, ConfigError> {
+ fn parse_tab_template_node(
+ &self,
+ kdl_node: &KdlNode,
+ ) -> Result<(PaneLayout, Vec<FloatingPanesLayout>), ConfigError> {
self.assert_valid_tab_properties(kdl_node)?;
let children_split_direction = self.parse_split_direction(kdl_node)?;
let mut tab_children = vec![];
+ let mut tab_floating_children = vec![];
let mut external_children_index = None;
+ let mut children_index_offset = 0;
if let Some(children) = kdl_children_nodes!(kdl_node) {
for (i, child) in children.iter().enumerate() {
if kdl_name!(child) == "pane" {
@@ -881,7 +1363,7 @@ impl<'a> KdlLayoutParser<'a> {
child.span().len(),
));
}
- external_children_index = Some(i);
+ external_children_index = Some(i.saturating_sub(children_index_offset));
} else if let Some((pane_template, pane_template_kdl_node)) =
self.pane_templates.get(kdl_name!(child)).cloned()
{
@@ -890,6 +1372,9 @@ impl<'a> KdlLayoutParser<'a> {
pane_template,
&pane_template_kdl_node,
)?);
+ } else if kdl_name!(child) == "floating_panes" {
+ children_index_offset += 1;
+ self.populate_floating_pane_children(child, &mut tab_floating_children)?;
} else if self.is_a_valid_tab_property(kdl_name!(child)) {
return Err(ConfigError::new_layout_kdl_error(
format!("Tab property '{}' must be placed on the tab_template title line and not in the child braces", kdl_name!(child)),
@@ -905,16 +1390,19 @@ impl<'a> KdlLayoutParser<'a> {
}
}
}
- Ok(PaneLayout {
- children_split_direction,
- children: tab_children,
- external_children_index,
- ..Default::default()
- })
+ Ok((
+ PaneLayout {
+ children_split_direction,
+ children: tab_children,
+ external_children_index,
+ ..Default::default()
+ },
+ tab_floating_children,
+ ))
}
fn default_template(&self) -> Result<Option<PaneLayout>, ConfigError> {
match &self.default_tab_template {
- Some((template, _kdl_node)) => {
+ Some((template, _template_floating_panes, _kdl_node)) => {
let mut template = template.clone();
if let Some(children_index) = template.external_children_index {
template
@@ -1058,7 +1546,7 @@ impl<'a> KdlLayoutParser<'a> {
}
fn layout_with_tabs(
&self,
- tabs: Vec<(Option<String>, PaneLayout)>,
+ tabs: Vec<(Option<String>, PaneLayout, Vec<FloatingPanesLayout>)>,
focused_tab_index: Option<usize>,
) -> Result<Layout, ConfigError> {
let template = self
@@ -1072,7 +1560,11 @@ impl<'a> KdlLayoutParser<'a> {
..Default::default()
})
}
- fn layout_with_one_tab(&self, panes: Vec<PaneLayout>) -> Result<Layout, ConfigError> {
+ fn layout_with_one_tab(
+ &self,
+ panes: Vec<PaneLayout>,
+ floating_panes: Vec<FloatingPanesLayout>,
+ ) -> Result<Layout, ConfigError> {
let main_tab_layout = PaneLayout {
children: panes,
..Default::default()
@@ -1083,47 +1575,55 @@ impl<'a> KdlLayoutParser<'a> {
// to explicitly place it in the first tab
vec![]
} else {
- vec![(None, main_tab_layout.clone())]
+ vec![(None, main_tab_layout.clone(), floating_panes.clone())]
};
let template = default_template.unwrap_or_else(|| main_tab_layout.clone());
// create a layout with one tab that has these child panes
Ok(Layout {
tabs,
template: Some(template),
+ floating_panes_template: floating_panes,
..Default::default()
})
}
- fn layout_with_one_pane(&self) -> Result<Layout, ConfigError> {
+ fn layout_with_one_pane(
+ &self,
+ child_floating_panes: Vec<FloatingPanesLayout>,
+ ) -> Result<Layout, ConfigError> {
let template = self
.default_template()?
.unwrap_or_else(|| PaneLayout::default());
Ok(Layout {
template: Some(template),
+ floating_panes_template: child_floating_panes,
..Default::default()
})
}
fn populate_layout_child(
&mut self,
child: &KdlNode,
- child_tabs: &mut Vec<(bool, Option<String>, PaneLayout)>,
+ child_tabs: &mut Vec<(bool, Option<String>, PaneLayout, Vec<FloatingPanesLayout>)>,
child_panes: &mut Vec<PaneLayout>,
+ child_floating_panes: &mut Vec<FloatingPanesLayout>,
) -> Result<(), ConfigError> {
let child_name = kdl_name!(child);
+ if (child_name == "pane" || child_name == "floating_panes") && !child_tabs.is_empty() {
+ return Err(ConfigError::new_layout_kdl_error(
+ "Cannot have both tabs and panes in the same node".into(),
+ child.span().offset(),
+ child.span().len(),
+ ));
+ }
if child_name == "pane" {
- if !child_tabs.is_empty() {
- return Err(ConfigError::new_layout_kdl_error(
- "Cannot have both tabs and panes in the same node".into(),
- child.span().offset(),
- child.span().len(),
- ));
- }
let mut pane_node = self.parse_pane_node(child)?;
if let Some(global_cwd) = &self.global_cwd {
pane_node.add_cwd_to_layout(&global_cwd);
}
child_panes.push(pane_node);
+ } else if child_name == "floating_panes" {
+ self.populate_floating_pane_children(child, child_floating_panes)?;
} else if child_name == "tab" {
- if !child_panes.is_empty() {
+ if !child_panes.is_empty() || !child_floating_panes.is_empty() {
return Err(ConfigError::new_layout_kdl_error(
"Cannot have both tabs and panes in the same node".into(),
child.span().offset(),
@@ -1131,11 +1631,16 @@ impl<'a> KdlLayoutParser<'a> {
));
}
match &self.default_tab_template {
- Some((default_tab_template, default_tab_template_kdl_node)) => {
+ Some((
+ default_tab_template,
+ default_tab_template_floating_panes,
+ default_tab_template_kdl_node,
+ )) => {
let default_tab_template = default_tab_template.clone();
child_tabs.push(self.parse_tab_node_with_template(
child,
default_tab_template,
+ default_tab_template_floating_panes.clone(),
default_tab_template_kdl_node,
)?);
},
@@ -1143,7 +1648,7 @@ impl<'a> KdlLayoutParser<'a> {
child_tabs.push(self.parse_tab_node(child)?);
},
}
- } else if let Some((tab_template, tab_template_kdl_node)) =
+ } else if let Some((tab_template, tab_template_floating_panes, tab_template_kdl_node)) =
self.tab_templates.get(child_name).cloned()
{
if !child_panes.is_empty() {
@@ -1156,6 +1661,7 @@ impl<'a> KdlLayoutParser<'a> {
child_tabs.push(self.parse_tab_node_with_template(
child,
tab_template,
+ tab_template_floating_panes,
&tab_template_kdl_node,
)?);
} else if let Some((pane_template, pane_template_kdl_node)) =
@@ -1183,6 +1689,43 @@ impl<'a> KdlLayoutParser<'a> {
}
Ok(())
}
+ fn populate_floating_pane_children(
+ &self,
+ child: &KdlNode,
+ child_floating_panes: &mut Vec<FloatingPanesLayout>,
+ ) -> Result<(), ConfigError> {
+ if let Some(children) = kdl_children_nodes!(child) {
+ for child in children {
+ if kdl_name!(child) == "pane" {
+ let mut pane_node = self.parse_floating_pane_node(child)?;
+ if let Some(global_cwd) = &self.global_cwd {
+ pane_node.add_cwd_to_layout(&global_cwd);
+ }
+ child_floating_panes.push(pane_node);
+ } else if let Some((pane_template, pane_template_kdl_node)) =
+ self.pane_templates.get(kdl_name!(child)).cloned()
+ {
+ let pane_node = self.parse_floating_pane_node_with_template(
+ child,
+ pane_template,
+ &pane_template_kdl_node,
+ )?;
+ child_floating_panes.push(pane_node);
+ } else {
+ // TODO: invalid node name
+ return Err(ConfigError::new_layout_kdl_error(
+ format!(
+ "floating_panes can only contain pane nodes, found: {}",
+ kdl_name!(child)
+ ),
+ child.span().offset(),
+ child.span().len(),
+ ));
+ }
+ }
+ };
+ Ok(())
+ }
pub fn parse(&mut self) -> Result<Layout, ConfigError> {
let kdl_layout: KdlDocument = self.raw_layout.parse()?;
let layout_node = kdl_layout
@@ -1209,18 +1752,24 @@ impl<'a> KdlLayoutParser<'a> {
}
let mut child_tabs = vec![];
let mut child_panes = vec![];
+ let mut child_floating_panes = vec![];
if let Some(children) = kdl_children_nodes!(layout_node) {
self.populate_global_cwd(layout_node)?;
self.populate_pane_templates(children, &kdl_layout)?;
self.populate_tab_templates(children)?;
for child in children {
- self.populate_layout_child(child, &mut child_tabs, &mut child_panes)?;
+ self.populate_layout_child(
+ child,
+ &mut child_tabs,
+ &mut child_panes,
+ &mut child_floating_panes,
+ )?;
}
}
if !child_tabs.is_empty() {
let has_more_than_one_focused_tab = child_tabs
.iter()
- .filter(|(is_focused, _, _)| *is_focused)
+ .filter(|(is_focused, _, _, _)| *is_focused)
.count()
> 1;
if has_more_than_one_focused_tab {
@@ -1230,16 +1779,23 @@ impl<'a> KdlLayoutParser<'a> {
kdl_layout.span().len(),
));
}
- let focused_tab_index = child_tabs.iter().position(|(is_focused, _, _)| *is_focused);
- let child_tabs: Vec<(Option<String>, PaneLayout)> = child_tabs
- .drain(..)
- .map(|(_is_focused, tab_name, pane_layout)| (tab_name, pane_layout))
- .collect();
+ let focused_tab_index = child_tabs
+ .iter()
+ .position(|(is_focused, _, _, _)| *is_focused);
+ let child_tabs: Vec<(Option<String>, PaneLayout, Vec<FloatingPanesLayout>)> =
+ child_tabs
+ .drain(..)
+ .map(
+ |(_is_focused, tab_name, pane_layout, floating_panes_layout)| {
+ (tab_name, pane_layout, floating_panes_layout)
+ },
+ )
+ .collect();
self.layout_with_tabs(child_tabs, focused_tab_index)
} else if !child_panes.is_empty() {
- self.layout_with_one_tab(child_panes)
+ self.layout_with_one_tab(child_panes, child_floating_panes)
} else {
- self.layout_with_one_pane()
+ self.layout_with_one_pane(child_floating_panes)
}
}
}
diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs
index b28a8cc3..db001fb5 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -738,7 +738,7 @@ impl TryFrom<&KdlNode> for Action {
"PaneNameInput" => {
parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action)
},
- "NewTab" => Ok(Action::NewTab(None, None)),
+ "NewTab" => Ok(Action::NewTab(None, vec![], None)),
"GoToTab" => parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action),
"TabNameInput" => {
parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action)
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap
index 263f3c60..8c6d5992 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/setup.rs
-assertion_line: 509
+assertion_line: 626
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -18,4 +18,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap
index d4ea72ea..8d89277c 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/setup.rs
-assertion_line: 492
+assertion_line: 583
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -79,4 +79,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
index 77914594..8d946d7b 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
@@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
+assertion_line: 582
expression: "format!(\"{:#?}\", config)"
---
Config {
@@ -975,6 +976,7 @@ Config {
): [
NewTab(
None,
+ [],
None,
),
SwitchToMode(
@@ -3049,6 +3051,7 @@ Config {
): [
NewTab(
None,
+ [],
None,
),
SwitchToMode(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
index 9d15cc6a..97fb9f81 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
@@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
+assertion_line: 640
expression: "format!(\"{:#?}\", config)"
---
Config {
@@ -975,6 +976,7 @@ Config {
): [
NewTab(
None,
+ [],
None,
),
SwitchToMode(
@@ -3049,6 +3051,7 @@ Config {
): [
NewTab(
None,
+ [],
None,
),
SwitchToMode(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap
index 3a4e696d..97b1c8b6 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/setup.rs
-assertion_line: 492
+assertion_line: 608
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -18,4 +18,5 @@ Layout {
external_children_index: None,
},
),
+ floating_panes_template: [],
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap
index aadd52ed..f1060512 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap
@@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
+assertion_line: 668
expression: "format!(\"{:#?}\", config)"
---
Config {
@@ -975,6 +976,7 @@ Config {
): [
NewTab(
None,
+ [],
None,
),
SwitchToMode(
@@ -3049,6 +3051,7 @@ Config {
): [
NewTab(
None,
+ [],
None,
),
SwitchToMode(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
index 46146fdf..13c499dd 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
@@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
+assertion_line: 682
expression: "format!(\"{:#?}\", config)"
---
Config {
@@ -975,6 +976,7 @@ Config {
): [
NewTab(
None,
+ [],
None,
),
SwitchToMode(
@@ -3049,6 +3051,7 @@ Config {
): [
NewTab(
None,
+ [],
None,
),
SwitchToMode(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
index 0f2e1e62..fc47de23 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
@@ -1,5 +1,6 @@
---
source: zellij-utils/src/setup.rs
+assertion_line: 654
expression: "format!(\"{:#?}\", config)"
---
Config {
@@ -975,6 +976,7 @@ Config {
): [
NewTab(
None,
+ [],
None,
),
SwitchToMode(
@@ -3049,6 +3051,7 @@ Config {
): [
NewTab(
None,
+ [],
None,
),
SwitchToMode(