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
This commit is contained in:
Aram Drevekenin 2022-12-24 15:48:04 +01:00 committed by GitHub
parent 17205793e4
commit 799fa5de8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 2051 additions and 576 deletions

View file

@ -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])),

View file

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

View file

@ -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()
}

View file

@ -539,11 +539,14 @@ impl TiledPanes {
viewport.cols = (viewport.cols as isize + column_difference) as usize;
display_area.cols = cols;
},
Err(e) => {
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) {
Ok(_) => {
@ -551,11 +554,14 @@ impl TiledPanes {
viewport.rows = (viewport.rows as isize + row_difference) as usize;
display_area.rows = rows;
},
Err(e) => {
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();
},
},
};
}
self.set_pane_frames(self.draw_pane_frames);

View file

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

View file

@ -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()
if let Some(new_pane_data) =
self.apply_run_instruction(run_instruction, default_shell.clone())?
{
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(),
new_pane_pids.push(new_pane_data);
}
} 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)
}
for run_instruction in extracted_floating_run_instructions {
if let Some(new_pane_data) =
self.apply_run_instruction(run_instruction, default_shell.clone())?
{
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(_)) => {},
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 {

View file

@ -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)?;
},

View file

@ -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()?;
},

View file

@ -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();
},
}
}
}
}

View file

@ -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}"
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,
)
};
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();
.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)?;
}
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(())
},
}
}
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),

View file

@ -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): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View file

@ -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,7 +345,19 @@ 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)
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();
@ -2519,6 +2535,34 @@ fn tab_with_basic_layout() {
assert_snapshot!(snapshot);
}
#[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#"

View file

@ -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,7 +232,14 @@ 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)
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();

View file

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

View file

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

View file

@ -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",
),

View file

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

View file

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

View file

@ -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)])
}
},
}

View file

@ -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()
}

View file

@ -89,6 +89,175 @@ fn layout_with_nested_panes() {
assert_eq!(layout, expected_layout);
}
#[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#"
@ -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()),

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

@ -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,10 +483,14 @@ 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");
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());
@ -431,6 +541,143 @@ impl<'a> KdlLayoutParser<'a> {
}
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(),
))
},
}
}
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)
},
}
}
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,10 +801,53 @@ 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 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)?,
@ -471,7 +857,7 @@ impl<'a> KdlLayoutParser<'a> {
self.pane_templates.insert(
template_name,
(
PaneLayout {
PaneOrFloatingPane::Pane(PaneLayout {
borderless: borderless.unwrap_or_default(),
focus,
split_size,
@ -480,17 +866,19 @@ impl<'a> KdlLayoutParser<'a> {
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 {
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" {
if !child_tabs.is_empty() {
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" {
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
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)| (tab_name, pane_layout))
.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)
}
}
}

View file

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

View file

@ -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: [],
}

View file

@ -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: [],
}

View file

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

View file

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

View file

@ -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: [],
}

View file

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

View file

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

View file

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