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![ vec![
(s("Move focus"), s("Move"), focus_keys), (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("Close"), s("Close"), action_key(&km, &[A::CloseTab, TO_NORMAL])),
(s("Rename"), s("Rename"), (s("Rename"), s("Rename"),
action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), 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 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("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("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"), (s("Rename tab"), s("Rename"),
action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])), action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])),
(s("Previous Tab"), s("Previous"), action_key(&km, &[A::GoToPreviousTab, TO_NORMAL])), (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 session_data
.read() .read()
.unwrap() .unwrap()
@ -342,6 +342,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.send_to_screen(ScreenInstruction::NewTab( .send_to_screen(ScreenInstruction::NewTab(
default_shell.clone(), default_shell.clone(),
tab_layout, tab_layout,
floating_panes_layout,
tab_name, tab_name,
client_id, client_id,
)) ))
@ -349,8 +350,12 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
}; };
if layout.has_tabs() { if layout.has_tabs() {
for (tab_name, tab_layout) in layout.tabs() { for (tab_name, tab_layout, floating_panes_layout) in layout.tabs() {
spawn_tabs(Some(tab_layout.clone()), tab_name); spawn_tabs(
Some(tab_layout.clone()),
floating_panes_layout.clone(),
tab_name,
);
} }
if let Some(focused_tab_index) = layout.focused_tab_index() { 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(); .unwrap();
} }
} else { } else {
spawn_tabs(None, None); spawn_tabs(None, layout.floating_panes_template.clone(), None);
} }
session_data session_data
.read() .read()

View file

@ -25,7 +25,8 @@ use zellij_utils::{
data::{ModeInfo, Style}, data::{ModeInfo, Style},
errors::prelude::*, errors::prelude::*,
input::command::RunCommand, 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; const RESIZE_INCREMENT_WIDTH: usize = 5;
@ -224,9 +225,55 @@ impl FloatingPanes {
); );
floating_pane_grid.find_room_for_new_pane() 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> { pub fn first_floating_pane_id(&self) -> Option<PaneId> {
self.panes.keys().next().copied() 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> { pub fn first_active_floating_pane_id(&self) -> Option<PaneId> {
self.active_panes.values().next().copied() self.active_panes.values().next().copied()
} }

View file

@ -539,10 +539,13 @@ impl TiledPanes {
viewport.cols = (viewport.cols as isize + column_difference) as usize; viewport.cols = (viewport.cols as isize + column_difference) as usize;
display_area.cols = cols; display_area.cols = cols;
}, },
Err(e) => { Err(e) => match e.downcast_ref::<ZellijError>() {
Err::<(), _>(anyError::msg(e)) Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout
.context("failed to resize tab horizontally") _ => {
.non_fatal(); Err::<(), _>(anyError::msg(e))
.context("failed to resize tab horizontally")
.non_fatal();
},
}, },
}; };
match pane_grid.layout(SplitDirection::Vertical, rows) { match pane_grid.layout(SplitDirection::Vertical, rows) {
@ -551,10 +554,13 @@ impl TiledPanes {
viewport.rows = (viewport.rows as isize + row_difference) as usize; viewport.rows = (viewport.rows as isize + row_difference) as usize;
display_area.rows = rows; display_area.rows = rows;
}, },
Err(e) => { Err(e) => match e.downcast_ref::<ZellijError>() {
Err::<(), _>(anyError::msg(e)) Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout
.context("failed to resize tab vertically") _ => {
.non_fatal(); Err::<(), _>(anyError::msg(e))
.context("failed to resize tab vertically")
.non_fatal();
},
}, },
}; };
} }

View file

@ -12,7 +12,7 @@ use zellij_utils::{
errors::{prelude::*, ContextType, PluginContext}, errors::{prelude::*, ContextType, PluginContext},
input::{ input::{
command::TerminalAction, command::TerminalAction,
layout::{Layout, PaneLayout, Run, RunPlugin, RunPluginLocation}, layout::{FloatingPanesLayout, Layout, PaneLayout, Run, RunPlugin, RunPluginLocation},
plugins::PluginsConfig, plugins::PluginsConfig,
}, },
pane_size::Size, pane_size::Size,
@ -29,6 +29,7 @@ pub enum PluginInstruction {
NewTab( NewTab(
Option<TerminalAction>, Option<TerminalAction>,
Option<PaneLayout>, Option<PaneLayout>,
Vec<FloatingPanesLayout>,
Option<String>, // tab name Option<String>, // tab name
usize, // tab_index usize, // tab_index
ClientId, 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"); let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin((&event).into())); err_ctx.add_call(ContextType::Plugin((&event).into()));
match event { match event {
// TODO: remove pid_tx from here
PluginInstruction::Load(run, tab_index, client_id, size) => { PluginInstruction::Load(run, tab_index, client_id, size) => {
wasm_bridge.load_plugin(&run, tab_index, size, client_id)?; wasm_bridge.load_plugin(&run, tab_index, size, client_id)?;
}, },
@ -91,16 +91,22 @@ pub(crate) fn plugin_thread_main(
PluginInstruction::NewTab( PluginInstruction::NewTab(
terminal_action, terminal_action,
tab_layout, tab_layout,
floating_panes_layout,
tab_name, tab_name,
tab_index, tab_index,
client_id, client_id,
) => { ) => {
let mut plugin_ids: HashMap<RunPluginLocation, Vec<u32>> = HashMap::new(); let mut plugin_ids: HashMap<RunPluginLocation, Vec<u32>> = HashMap::new();
let extracted_run_instructions = tab_layout let mut extracted_run_instructions = tab_layout
.clone() .clone()
.unwrap_or_else(|| layout.new_tab()) .unwrap_or_else(|| layout.new_tab().0)
.extract_run_instructions(); .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 { for run_instruction in extracted_run_instructions {
if let Some(Run::Plugin(run)) = run_instruction { if let Some(Run::Plugin(run)) = run_instruction {
let plugin_id = let plugin_id =
@ -111,6 +117,7 @@ pub(crate) fn plugin_thread_main(
drop(bus.senders.send_to_pty(PtyInstruction::NewTab( drop(bus.senders.send_to_pty(PtyInstruction::NewTab(
terminal_action, terminal_action,
tab_layout, tab_layout,
floating_panes_layout,
tab_name, tab_name,
tab_index, tab_index,
plugin_ids, plugin_ids,

View file

@ -15,7 +15,7 @@ use zellij_utils::{
errors::{ContextType, PtyContext}, errors::{ContextType, PtyContext},
input::{ input::{
command::{RunCommand, TerminalAction}, command::{RunCommand, TerminalAction},
layout::{Layout, PaneLayout, Run, RunPluginLocation}, layout::{FloatingPanesLayout, Layout, PaneLayout, Run, RunPluginLocation},
}, },
}; };
@ -50,6 +50,7 @@ pub enum PtyInstruction {
NewTab( NewTab(
Option<TerminalAction>, Option<TerminalAction>,
Option<PaneLayout>, Option<PaneLayout>,
Vec<FloatingPanesLayout>,
Option<String>, Option<String>,
usize, // tab_index usize, // tab_index
HashMap<RunPluginLocation, Vec<u32>>, // plugin_ids 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( PtyInstruction::NewTab(
terminal_action, terminal_action,
tab_layout, tab_layout,
floating_panes_layout,
tab_name, tab_name,
tab_index, tab_index,
plugin_ids, 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 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( 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(), terminal_action.clone(),
plugin_ids, plugin_ids,
tab_index, tab_index,
@ -562,6 +570,7 @@ impl Pty {
pub fn spawn_terminals_for_layout( pub fn spawn_terminals_for_layout(
&mut self, &mut self,
layout: PaneLayout, layout: PaneLayout,
floating_panes_layout: Vec<FloatingPanesLayout>,
default_shell: Option<TerminalAction>, default_shell: Option<TerminalAction>,
plugin_ids: HashMap<RunPluginLocation, Vec<u32>>, plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
tab_index: usize, tab_index: usize,
@ -572,176 +581,28 @@ impl Pty {
let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal(None)); let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal(None));
self.fill_cwd(&mut default_shell, client_id); self.fill_cwd(&mut default_shell, client_id);
let extracted_run_instructions = layout.extract_run_instructions(); 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, let mut new_pane_pids: Vec<(u32, bool, Option<RunCommand>, Result<RawFd>)> = vec![]; // (terminal_id,
// starts_held, // starts_held,
// run_command, // run_command,
// file_descriptor) // 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 { for run_instruction in extracted_run_instructions {
let quit_cb = Box::new({ if let Some(new_pane_data) =
let senders = self.bus.senders.clone(); self.apply_run_instruction(run_instruction, default_shell.clone())?
move |pane_id, _exit_status, _command| { {
let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); new_pane_pids.push(new_pane_data);
} }
}); }
match run_instruction { for run_instruction in extracted_floating_run_instructions {
Some(Run::Command(command)) => { if let Some(new_pane_data) =
let starts_held = command.hold_on_start; self.apply_run_instruction(run_instruction, default_shell.clone())?
let hold_on_close = command.hold_on_close; {
let quit_cb = Box::new({ new_floating_panes_pids.push(new_pane_data);
let senders = self.bus.senders.clone();
move |pane_id, exit_status, command| {
if hold_on_close {
let _ = senders.send_to_screen(ScreenInstruction::HoldPane(
pane_id,
exit_status,
command,
None,
));
} else {
let _ = senders
.send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
}
}
});
let cmd = TerminalAction::RunCommand(command.clone());
if starts_held {
// we don't actually open a terminal in this case, just wait for the user to run it
match self
.bus
.os_input
.as_mut()
.context("no OS I/O interface found")
.with_context(err_context)?
.reserve_terminal_id()
{
Ok(terminal_id) => {
new_pane_pids.push((
terminal_id,
starts_held,
Some(command.clone()),
Ok(terminal_id as i32), // this is not actually correct but gets
// stripped later
));
},
Err(e) => Err::<(), _>(e).with_context(err_context).non_fatal(),
}
} else {
match self
.bus
.os_input
.as_mut()
.context("no OS I/O interface found")
.with_context(err_context)?
.spawn_terminal(cmd, quit_cb, self.default_editor.clone())
.with_context(err_context)
{
Ok((terminal_id, pid_primary, child_fd)) => {
self.id_to_child_pid.insert(terminal_id, child_fd);
new_pane_pids.push((
terminal_id,
starts_held,
Some(command.clone()),
Ok(pid_primary),
));
},
Err(err) => match err.downcast_ref::<ZellijError>() {
Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
new_pane_pids.push((
*terminal_id,
starts_held,
Some(command.clone()),
Err(err),
));
},
_ => {
Err::<(), _>(err).non_fatal();
},
},
}
}
},
Some(Run::Cwd(cwd)) => {
let starts_held = false; // we do not hold Cwd panes
let shell = self.get_default_terminal(Some(cwd));
match self
.bus
.os_input
.as_mut()
.context("no OS I/O interface found")
.with_context(err_context)?
.spawn_terminal(shell, quit_cb, self.default_editor.clone())
.with_context(err_context)
{
Ok((terminal_id, pid_primary, child_fd)) => {
self.id_to_child_pid.insert(terminal_id, child_fd);
new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
},
Err(err) => match err.downcast_ref::<ZellijError>() {
Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
new_pane_pids.push((*terminal_id, starts_held, None, Err(err)));
},
_ => {
Err::<(), _>(err).non_fatal();
},
},
}
},
Some(Run::EditFile(path_to_file, line_number)) => {
let starts_held = false; // we do not hold edit panes (for now?)
match self
.bus
.os_input
.as_mut()
.context("no OS I/O interface found")
.with_context(err_context)?
.spawn_terminal(
TerminalAction::OpenFile(path_to_file, line_number),
quit_cb,
self.default_editor.clone(),
)
.with_context(err_context)
{
Ok((terminal_id, pid_primary, child_fd)) => {
self.id_to_child_pid.insert(terminal_id, child_fd);
new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
},
Err(err) => match err.downcast_ref::<ZellijError>() {
Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
new_pane_pids.push((*terminal_id, starts_held, None, Err(err)));
},
_ => {
Err::<(), _>(err).non_fatal();
},
},
}
},
None => {
let starts_held = false;
match self
.bus
.os_input
.as_mut()
.context("no OS I/O interface found")
.with_context(err_context)?
.spawn_terminal(default_shell.clone(), quit_cb, self.default_editor.clone())
.with_context(err_context)
{
Ok((terminal_id, pid_primary, child_fd)) => {
self.id_to_child_pid.insert(terminal_id, child_fd);
new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
},
Err(err) => match err.downcast_ref::<ZellijError>() {
Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
new_pane_pids.push((*terminal_id, starts_held, None, Err(err)));
},
_ => {
Err::<(), _>(err).non_fatal();
},
},
}
},
// Investigate moving plugin loading to here.
Some(Run::Plugin(_)) => {},
} }
} }
// Option<RunCommand> should only be Some if the pane starts held // Option<RunCommand> should only be Some if the pane starts held
@ -755,17 +616,32 @@ impl Pty {
} }
}) })
.collect(); .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 self.bus
.senders .senders
.send_to_screen(ScreenInstruction::ApplyLayout( .send_to_screen(ScreenInstruction::ApplyLayout(
layout, layout,
floating_panes_layout,
new_tab_pane_ids, new_tab_pane_ids,
new_tab_floating_pane_ids,
plugin_ids, plugin_ids,
tab_index, tab_index,
client_id, client_id,
)) ))
.with_context(err_context)?; .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 { if starts_held {
// we do not run a command or start listening for bytes on held panes // we do not run a command or start listening for bytes on held panes
continue; continue;
@ -820,6 +696,172 @@ impl Pty {
} }
Ok(()) 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<()> { pub fn close_pane(&mut self, id: PaneId) -> Result<()> {
let err_context = || format!("failed to close for pane {id:?}"); let err_context = || format!("failed to close for pane {id:?}");
match id { match id {

View file

@ -430,12 +430,16 @@ pub(crate) fn route_action(
.send_to_screen(ScreenInstruction::CloseFocusedPane(client_id)) .send_to_screen(ScreenInstruction::CloseFocusedPane(client_id))
.with_context(err_context)?; .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(); let shell = session.default_shell.clone();
session session
.senders .senders
.send_to_screen(ScreenInstruction::NewTab( .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)?; .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::pane_size::{Size, SizeInPixels};
use zellij_utils::{ use zellij_utils::{
input::command::TerminalAction, input::command::TerminalAction,
input::layout::{PaneLayout, RunPluginLocation}, input::layout::{FloatingPanesLayout, PaneLayout, RunPluginLocation},
position::Position, position::Position,
}; };
@ -177,12 +177,15 @@ pub enum ScreenInstruction {
NewTab( NewTab(
Option<TerminalAction>, Option<TerminalAction>,
Option<PaneLayout>, Option<PaneLayout>,
Vec<FloatingPanesLayout>,
Option<String>, Option<String>,
ClientId, ClientId,
), ),
ApplyLayout( ApplyLayout(
PaneLayout, PaneLayout,
Vec<(u32, HoldForCommand)>, Vec<FloatingPanesLayout>,
Vec<(u32, HoldForCommand)>, // new pane pids
Vec<(u32, HoldForCommand)>, // new floating pane pids
HashMap<RunPluginLocation, Vec<u32>>, HashMap<RunPluginLocation, Vec<u32>>,
usize, // tab_index usize, // tab_index
ClientId, ClientId,
@ -920,7 +923,9 @@ impl Screen {
pub fn apply_layout( pub fn apply_layout(
&mut self, &mut self,
layout: PaneLayout, layout: PaneLayout,
floating_panes_layout: Vec<FloatingPanesLayout>,
new_terminal_ids: Vec<(u32, HoldForCommand)>, new_terminal_ids: Vec<(u32, HoldForCommand)>,
new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>, new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
tab_index: usize, tab_index: usize,
client_id: ClientId, client_id: ClientId,
@ -968,9 +973,10 @@ impl Screen {
let tab = self.tabs.get_mut(&tab_index).unwrap(); // TODO: no unwrap let tab = self.tabs.get_mut(&tab_index).unwrap(); // TODO: no unwrap
tab.apply_layout( tab.apply_layout(
layout, layout,
floating_panes_layout,
new_terminal_ids, new_terminal_ids,
new_floating_terminal_ids,
new_plugin_ids, new_plugin_ids,
tab_index,
client_id, client_id,
) )
.with_context(err_context)?; .with_context(err_context)?;
@ -1878,7 +1884,13 @@ pub(crate) fn screen_thread_main(
screen.unblock_input()?; screen.unblock_input()?;
screen.render()?; 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(); let tab_index = screen.get_new_tab_index();
screen.new_tab(tab_index, client_id)?; screen.new_tab(tab_index, client_id)?;
screen screen
@ -1887,6 +1899,7 @@ pub(crate) fn screen_thread_main(
.send_to_plugin(PluginInstruction::NewTab( .send_to_plugin(PluginInstruction::NewTab(
default_shell, default_shell,
layout, layout,
floating_panes_layout,
tab_name, tab_name,
tab_index, tab_index,
client_id, client_id,
@ -1894,12 +1907,22 @@ pub(crate) fn screen_thread_main(
}, },
ScreenInstruction::ApplyLayout( ScreenInstruction::ApplyLayout(
layout, layout,
floating_panes_layout,
new_pane_pids, new_pane_pids,
new_floating_pane_pids,
new_plugin_ids, new_plugin_ids,
tab_index, tab_index,
client_id, 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.unblock_input()?;
screen.render()?; 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 clipboard;
mod copy_command; mod copy_command;
mod layout_applier;
use copy_command::CopyCommand; use copy_command::CopyCommand;
use std::env::temp_dir; use std::env::temp_dir;
@ -17,6 +18,7 @@ use crate::background_jobs::BackgroundJob;
use crate::pty_writer::PtyWriteInstruction; use crate::pty_writer::PtyWriteInstruction;
use crate::screen::CopyOptions; use crate::screen::CopyOptions;
use crate::ui::pane_boundaries_frame::FrameParams; use crate::ui::pane_boundaries_frame::FrameParams;
use layout_applier::LayoutApplier;
use self::clipboard::ClipboardProvider; use self::clipboard::ClipboardProvider;
use crate::{ use crate::{
@ -24,7 +26,7 @@ use crate::{
output::{CharacterChunk, Output, SixelImageChunk}, output::{CharacterChunk, Output, SixelImageChunk},
panes::sixel::SixelImageStore, panes::sixel::SixelImageStore,
panes::{FloatingPanes, TiledPanes}, panes::{FloatingPanes, TiledPanes},
panes::{LinkHandler, PaneId, PluginPane, TerminalPane}, panes::{LinkHandler, PaneId, TerminalPane},
plugins::PluginInstruction, plugins::PluginInstruction,
pty::{ClientOrTabIndex, PtyInstruction, VteBytes}, pty::{ClientOrTabIndex, PtyInstruction, VteBytes},
thread_bus::ThreadSenders, thread_bus::ThreadSenders,
@ -42,7 +44,7 @@ use zellij_utils::{
data::{Event, InputMode, ModeInfo, Palette, PaletteColor, Style}, data::{Event, InputMode, ModeInfo, Palette, PaletteColor, Style},
input::{ input::{
command::TerminalAction, command::TerminalAction,
layout::{PaneLayout, Run, RunPluginLocation}, layout::{FloatingPanesLayout, PaneLayout, RunPluginLocation},
parse_keys, parse_keys,
}, },
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport}, pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
@ -398,6 +400,26 @@ pub enum AdjustedInput {
ReRunCommandInThisPane(RunCommand), ReRunCommandInThisPane(RunCommand),
CloseThisPane, 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 { impl Tab {
// FIXME: Still too many arguments for clippy to be happy... // FIXME: Still too many arguments for clippy to be happy...
@ -509,170 +531,45 @@ impl Tab {
pub fn apply_layout( pub fn apply_layout(
&mut self, &mut self,
layout: PaneLayout, layout: PaneLayout,
floating_panes_layout: Vec<FloatingPanesLayout>,
new_terminal_ids: Vec<(u32, HoldForCommand)>, new_terminal_ids: Vec<(u32, HoldForCommand)>,
mut new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>, new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
tab_index: usize, new_plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
client_id: ClientId, client_id: ClientId,
) -> Result<()> { ) -> Result<()> {
let err_context = || { let layout_has_floating_panes = LayoutApplier::new(
format!( &self.viewport,
"failed to apply layout {layout:#?} in tab {tab_index} for client id {client_id}" &self.senders,
) &self.sixel_image_store,
}; &self.link_handler,
&self.terminal_emulator_colors,
if self.tiled_panes.has_panes() { &self.terminal_emulator_color_codes,
Err::<(), _>(anyhow!( &self.character_cell_size,
"Applying a layout to a tab with existing panes - this is not yet supported!" &self.style,
)) &self.display_area,
.with_context(err_context) &mut self.tiled_panes,
.non_fatal(); &mut self.floating_panes,
} self.draw_pane_frames,
let (viewport_cols, viewport_rows) = { &mut self.focus_pane_id,
let viewport = self.viewport.borrow(); &self.os_api,
(viewport.cols, viewport.rows) )
}; .apply_layout(
let mut free_space = PaneGeom::default(); layout,
free_space.cols.set_inner(viewport_cols); floating_panes_layout,
free_space.rows.set_inner(viewport_rows); new_terminal_ids,
new_floating_terminal_ids,
match layout.position_panes_in_space(&free_space) { new_plugin_ids,
Ok(positions_in_layout) => { client_id,
let positions_and_size = positions_in_layout.iter(); )?;
let mut new_terminal_ids = new_terminal_ids.iter(); if layout_has_floating_panes {
if !self.floating_panes.panes_are_visible() {
let mut focus_pane_id: Option<PaneId> = None; self.toggle_floating_panes(client_id, 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(())
},
} }
self.tiled_panes.set_pane_frames(self.draw_pane_frames);
self.is_pending = false;
self.apply_buffered_instructions()?;
Ok(())
} }
pub fn apply_buffered_instructions(&mut self) -> Result<()> { pub fn apply_buffered_instructions(&mut self) -> Result<()> {
let buffered_instructions: Vec<BufferedTabInstruction> = let buffered_instructions: Vec<BufferedTabInstruction> =
@ -852,7 +749,7 @@ impl Tab {
self.set_force_render(); self.set_force_render();
} else { } else {
self.show_floating_panes(); 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) => { Some(first_floating_pane_id) => {
if !self.floating_panes.active_panes_contain(&client_id) { if !self.floating_panes.active_panes_contain(&client_id) {
self.floating_panes self.floating_panes
@ -2708,30 +2605,6 @@ impl Tab {
Ok(()) 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<()> { pub fn visible(&self, visible: bool) -> Result<()> {
let pids_in_this_tab = self.tiled_panes.pane_ids().filter_map(|p| match p { let pids_in_this_tab = self.tiled_panes.pane_ids().filter_map(|p| match p {
PaneId::Plugin(pid) => Some(pid), 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( tab.apply_layout(
PaneLayout::default(), PaneLayout::default(),
vec![],
vec![(1, None)], vec![(1, None)],
vec![],
HashMap::new(), HashMap::new(),
index,
client_id, client_id,
) )
.unwrap(); .unwrap();
@ -285,9 +286,10 @@ fn create_new_tab_with_os_api(
); );
tab.apply_layout( tab.apply_layout(
PaneLayout::default(), PaneLayout::default(),
vec![],
vec![(1, None)], vec![(1, None)],
vec![],
HashMap::new(), HashMap::new(),
index,
client_id, client_id,
) )
.unwrap(); .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 terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let layout = Layout::from_str(layout, "layout_file_name".into(), None).unwrap(); 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( let mut tab = Tab::new(
index, index,
position, position,
@ -343,8 +345,20 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
.enumerate() .enumerate()
.map(|(i, _)| (i as u32, None)) .map(|(i, _)| (i as u32, None))
.collect(); .collect();
tab.apply_layout(tab_layout, pane_ids, HashMap::new(), index, client_id) let floating_pane_ids = floating_panes_layout
.unwrap(); .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 tab
} }
@ -396,9 +410,10 @@ fn create_new_tab_with_mock_pty_writer(
); );
tab.apply_layout( tab.apply_layout(
PaneLayout::default(), PaneLayout::default(),
vec![],
vec![(1, None)], vec![(1, None)],
vec![],
HashMap::new(), HashMap::new(),
index,
client_id, client_id,
) )
.unwrap(); .unwrap();
@ -455,9 +470,10 @@ fn create_new_tab_with_sixel_support(
); );
tab.apply_layout( tab.apply_layout(
PaneLayout::default(), PaneLayout::default(),
vec![],
vec![(1, None)], vec![(1, None)],
vec![],
HashMap::new(), HashMap::new(),
index,
client_id, client_id,
) )
.unwrap(); .unwrap();
@ -2519,6 +2535,34 @@ fn tab_with_basic_layout() {
assert_snapshot!(snapshot); 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] #[test]
fn tab_with_nested_layout() { fn tab_with_nested_layout() {
let layout = r#" let layout = r#"

View file

@ -178,9 +178,10 @@ fn create_new_tab(size: Size) -> Tab {
); );
tab.apply_layout( tab.apply_layout(
PaneLayout::default(), PaneLayout::default(),
vec![],
vec![(1, None)], vec![(1, None)],
vec![],
HashMap::new(), HashMap::new(),
index,
client_id, client_id,
) )
.unwrap(); .unwrap();
@ -231,8 +232,15 @@ fn create_new_tab_with_layout(size: Size, layout: PaneLayout) -> Tab {
for i in 0..layout.extract_run_instructions().len() { for i in 0..layout.extract_run_instructions().len() {
new_terminal_ids.push((i as u32, None)); new_terminal_ids.push((i as u32, None));
} }
tab.apply_layout(layout, new_terminal_ids, HashMap::new(), index, client_id) tab.apply_layout(
.unwrap(); layout,
vec![],
new_terminal_ids,
vec![],
HashMap::new(),
client_id,
)
.unwrap();
tab tab
} }
@ -280,9 +288,10 @@ fn create_new_tab_with_cell_size(
); );
tab.apply_layout( tab.apply_layout(
PaneLayout::default(), PaneLayout::default(),
vec![],
vec![(1, None)], vec![(1, None)],
vec![],
HashMap::new(), HashMap::new(),
index,
client_id, client_id,
) )
.unwrap(); .unwrap();

View file

@ -14,7 +14,7 @@ use zellij_utils::data::Resize;
use zellij_utils::errors::{prelude::*, ErrorContext}; use zellij_utils::errors::{prelude::*, ErrorContext};
use zellij_utils::input::actions::Action; use zellij_utils::input::actions::Action;
use zellij_utils::input::command::{RunCommand, TerminalAction}; 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::input::options::Options;
use zellij_utils::ipc::IpcReceiverWithContext; use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::{Size, SizeInPixels}; use zellij_utils::pane_size::{Size, SizeInPixels};
@ -297,12 +297,15 @@ impl MockScreen {
let _ = self.to_screen.send(ScreenInstruction::NewTab( let _ = self.to_screen.send(ScreenInstruction::NewTab(
default_shell, default_shell,
Some(pane_layout.clone()), Some(pane_layout.clone()),
vec![], // floating_panes_layout
tab_name, tab_name,
self.main_client_id, self.main_client_id,
)); ));
let _ = self.to_screen.send(ScreenInstruction::ApplyLayout( let _ = self.to_screen.send(ScreenInstruction::ApplyLayout(
pane_layout, pane_layout,
vec![], // floating panes layout
pane_ids, pane_ids,
vec![], // floating pane ids
plugin_ids, plugin_ids,
tab_index, tab_index,
self.main_client_id, self.main_client_id,
@ -323,12 +326,15 @@ impl MockScreen {
let _ = self.to_screen.send(ScreenInstruction::NewTab( let _ = self.to_screen.send(ScreenInstruction::NewTab(
default_shell, default_shell,
Some(tab_layout.clone()), Some(tab_layout.clone()),
vec![], // floating_panes_layout
tab_name, tab_name,
self.main_client_id, self.main_client_id,
)); ));
let _ = self.to_screen.send(ScreenInstruction::ApplyLayout( let _ = self.to_screen.send(ScreenInstruction::ApplyLayout(
tab_layout, tab_layout,
vec![], // floating_panes_layout
pane_ids, pane_ids,
vec![], // floating panes ids
plugin_ids, plugin_ids,
0, 0,
self.main_client_id, self.main_client_id,
@ -472,7 +478,9 @@ fn new_tab(screen: &mut Screen, pid: u32, tab_index: usize) {
screen screen
.apply_layout( .apply_layout(
PaneLayout::default(), PaneLayout::default(),
vec![], // floating panes layout
new_terminal_ids, new_terminal_ids,
vec![], // new floating terminal ids
new_plugin_ids, new_plugin_ids,
tab_index, tab_index,
client_id, client_id,

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2272 assertion_line: 2300
expression: "format!(\"{:#?}\", new_tab_action)" expression: "format!(\"{:#?}\", new_tab_action)"
--- ---
Some( Some(
@ -39,6 +39,7 @@ Some(
external_children_index: None, external_children_index: None,
}, },
), ),
[],
None, None,
0, 0,
1, 1,

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2322 assertion_line: 2350
expression: "format!(\"{:#?}\", new_tab_instruction)" expression: "format!(\"{:#?}\", new_tab_instruction)"
--- ---
NewTab( NewTab(
@ -60,6 +60,7 @@ NewTab(
external_children_index: None, external_children_index: None,
}, },
), ),
[],
Some( Some(
"my-awesome-tab-name", "my-awesome-tab-name",
), ),

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2511 assertion_line: 2534
expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
--- ---
[ [
@ -61,6 +61,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
external_children_index: None, external_children_index: None,
}, },
), ),
[],
None, None,
0, 0,
1, 1,
@ -207,6 +208,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
external_children_index: None, external_children_index: None,
}, },
), ),
[],
None, None,
1, 1,
1, 1,

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2554 assertion_line: 2577
expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
--- ---
[ [
@ -61,6 +61,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
external_children_index: None, external_children_index: None,
}, },
), ),
[],
None, None,
0, 0,
1, 1,
@ -207,6 +208,7 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())"
external_children_index: None, external_children_index: None,
}, },
), ),
[],
None, None,
1, 1,
1, 1,

View file

@ -1,7 +1,7 @@
//! Definition of the actions that can be bound to keys. //! Definition of the actions that can be bound to keys.
use super::command::RunCommandAction; use super::command::RunCommandAction;
use super::layout::{Layout, PaneLayout}; use super::layout::{FloatingPanesLayout, Layout, PaneLayout};
use crate::cli::CliAction; use crate::cli::CliAction;
use crate::data::InputMode; use crate::data::InputMode;
use crate::data::{Direction, Resize}; use crate::data::{Direction, Resize};
@ -162,7 +162,7 @@ pub enum Action {
PaneNameInput(Vec<u8>), PaneNameInput(Vec<u8>),
UndoRenamePane, UndoRenamePane,
/// Create a new tab, optionally with a specified tab layout. /// 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. /// Do nothing.
NoOp, NoOp,
/// Go to the next tab. /// Go to the next tab.
@ -368,15 +368,24 @@ impl Action {
if tabs.len() > 1 { if tabs.len() > 1 {
return Err(format!("Tab layout cannot itself have tabs")); return Err(format!("Tab layout cannot itself have tabs"));
} else if !tabs.is_empty() { } 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); let name = tab_name.or(name);
Ok(vec![Action::NewTab(Some(layout), name)]) Ok(vec![Action::NewTab(
Some(layout),
floating_panes_layout,
name,
)])
} else { } else {
let layout = layout.new_tab(); let (layout, floating_panes_layout) = layout.new_tab();
Ok(vec![Action::NewTab(Some(layout), name)]) Ok(vec![Action::NewTab(
Some(layout),
floating_panes_layout,
name,
)])
} }
} else { } 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)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
pub struct Layout { pub struct Layout {
pub tabs: Vec<(Option<String>, PaneLayout)>, pub tabs: Vec<(Option<String>, PaneLayout, Vec<FloatingPanesLayout>)>,
pub focused_tab_index: Option<usize>, pub focused_tab_index: Option<usize>,
pub template: Option<PaneLayout>, 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)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
@ -276,6 +361,8 @@ impl PaneLayout {
Ok(layouts) Ok(layouts)
} }
pub fn extract_run_instructions(&self) -> Vec<Option<Run>> { 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![]; let mut run_instructions = vec![];
if self.children.is_empty() { if self.children.is_empty() {
run_instructions.push(self.run.clone()); run_instructions.push(self.run.clone());
@ -452,11 +539,12 @@ impl Layout {
Ok(String::from_utf8(setup::COMPACT_BAR_LAYOUT.to_vec())?) Ok(String::from_utf8(setup::COMPACT_BAR_LAYOUT.to_vec())?)
} }
pub fn new_tab(&self) -> PaneLayout { pub fn new_tab(&self) -> (PaneLayout, Vec<FloatingPanesLayout>) {
match &self.template { let template = match &self.template {
Some(template) => template.clone(), Some(template) => template.clone(),
None => PaneLayout::default(), None => PaneLayout::default(),
} };
(template, self.floating_panes_template.clone())
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
@ -467,7 +555,7 @@ impl Layout {
!self.tabs.is_empty() !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 // String is the tab name
self.tabs.clone() self.tabs.clone()
} }

View file

@ -89,6 +89,175 @@ fn layout_with_nested_panes() {
assert_eq!(layout, expected_layout); 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] #[test]
fn layout_with_tabs() { fn layout_with_tabs() {
let kdl_layout = r#" 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 layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
let expected_layout = Layout { let expected_layout = Layout {
tabs: vec![(None, PaneLayout::default())], tabs: vec![(None, PaneLayout::default(), vec![])],
template: Some(PaneLayout::default()), template: Some(PaneLayout::default()),
..Default::default() ..Default::default()
}; };
@ -134,6 +303,7 @@ fn layout_with_nested_differing_tabs() {
], ],
..Default::default() ..Default::default()
}, },
vec![], // floating panes
), ),
( (
None, None,
@ -142,6 +312,7 @@ fn layout_with_nested_differing_tabs() {
children: vec![PaneLayout::default(), PaneLayout::default()], children: vec![PaneLayout::default(), PaneLayout::default()],
..Default::default() ..Default::default()
}, },
vec![], // floating panes
), ),
], ],
template: Some(PaneLayout::default()), template: Some(PaneLayout::default()),
@ -412,6 +583,7 @@ fn layout_with_tab_names() {
children: vec![], children: vec![],
..Default::default() ..Default::default()
}, },
vec![], // floating panes
), ),
( (
Some("my cool tab name 2".into()), Some("my cool tab name 2".into()),
@ -419,6 +591,7 @@ fn layout_with_tab_names() {
children: vec![], children: vec![],
..Default::default() ..Default::default()
}, },
vec![], // floating panes
), ),
], ],
template: Some(PaneLayout::default()), 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 layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
let expected_layout = Layout { let expected_layout = Layout {
tabs: vec![ tabs: vec![
(None, PaneLayout::default()), (None, PaneLayout::default(), vec![]),
(None, PaneLayout::default()), (None, PaneLayout::default(), vec![]),
(None, PaneLayout::default()), (None, PaneLayout::default(), vec![]),
], ],
template: Some(PaneLayout::default()), template: Some(PaneLayout::default()),
focused_tab_index: Some(1), focused_tab_index: Some(1),
@ -488,6 +661,7 @@ fn layout_with_tab_templates() {
], ],
..Default::default() ..Default::default()
}, },
vec![], // floating panes
), ),
( (
Some("my second tab".into()), Some("my second tab".into()),
@ -504,6 +678,7 @@ fn layout_with_tab_templates() {
], ],
..Default::default() ..Default::default()
}, },
vec![], // floating panes
), ),
( (
None, None,
@ -516,6 +691,7 @@ fn layout_with_tab_templates() {
], ],
..Default::default() ..Default::default()
}, },
vec![], // floating panes
), ),
], ],
template: Some(PaneLayout::default()), template: Some(PaneLayout::default()),

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1081 assertion_line: 1101
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -62,4 +62,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1046 assertion_line: 1066
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -65,4 +65,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 711 assertion_line: 744
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -138,4 +138,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 801 assertion_line: 696
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -82,6 +82,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
( (
None, None,
@ -129,6 +130,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
], ],
focused_tab_index: None, focused_tab_index: None,
@ -144,4 +146,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1098 assertion_line: 1118
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -59,4 +59,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1064 assertion_line: 1084
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -59,4 +59,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 899 assertion_line: 796
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -105,6 +105,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
( (
None, None,
@ -183,6 +184,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
], ],
focused_tab_index: None, focused_tab_index: None,
@ -198,4 +200,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1133 assertion_line: 1153
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -61,4 +61,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1116 assertion_line: 1136
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -63,4 +63,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1516 assertion_line: 1536
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -58,6 +58,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
], ],
focused_tab_index: None, focused_tab_index: None,
@ -73,4 +74,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1537 assertion_line: 1557
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -102,6 +102,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
], ],
focused_tab_index: None, focused_tab_index: None,
@ -117,4 +118,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1556 assertion_line: 1576
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -87,6 +87,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
], ],
focused_tab_index: None, focused_tab_index: None,
@ -102,4 +103,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1403 assertion_line: 1423
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -55,4 +55,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1435 assertion_line: 1455
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -55,4 +55,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1455 assertion_line: 1475
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -55,4 +55,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1416 assertion_line: 1436
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -55,4 +55,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1471 assertion_line: 1491
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -58,6 +58,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
], ],
focused_tab_index: None, focused_tab_index: None,
@ -73,4 +74,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 281 assertion_line: 283
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -39,4 +39,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 294 assertion_line: 296
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -39,4 +39,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 549 assertion_line: 555
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -71,6 +71,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
( (
Some( Some(
@ -138,6 +139,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
( (
None, None,
@ -182,6 +184,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
], ],
focused_tab_index: None, focused_tab_index: None,
@ -228,4 +231,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 756 assertion_line: 650
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -122,4 +122,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 729 assertion_line: 624
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -91,4 +91,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 626 assertion_line: 582
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -236,4 +236,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 583 assertion_line: 603
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -80,6 +80,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
], ],
focused_tab_index: None, focused_tab_index: None,
@ -95,4 +96,5 @@ Layout {
external_children_index: None, 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 source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1287 assertion_line: 1307
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -41,4 +41,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1233 assertion_line: 1253
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -41,4 +41,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1250 assertion_line: 1270
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -41,4 +41,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1268 assertion_line: 1288
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -41,4 +41,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1305 assertion_line: 1325
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -41,4 +41,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1235 assertion_line: 1343
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -33,4 +33,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1357 assertion_line: 1377
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -41,4 +41,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1339 assertion_line: 1359
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -41,4 +41,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1298 assertion_line: 1393
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -34,4 +34,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1314 assertion_line: 1409
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -34,4 +34,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1486 assertion_line: 1506
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -58,6 +58,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
], ],
focused_tab_index: None, focused_tab_index: None,
@ -73,4 +74,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/input/./unit/layout_test.rs source: zellij-utils/src/input/./unit/layout_test.rs
assertion_line: 1501 assertion_line: 1521
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -58,6 +58,7 @@ Layout {
focus: None, focus: None,
external_children_index: None, external_children_index: None,
}, },
[],
), ),
], ],
focused_tab_index: None, focused_tab_index: None,
@ -73,4 +74,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

File diff suppressed because it is too large Load diff

View file

@ -738,7 +738,7 @@ impl TryFrom<&KdlNode> for Action {
"PaneNameInput" => { "PaneNameInput" => {
parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action) 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), "GoToTab" => parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action),
"TabNameInput" => { "TabNameInput" => {
parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action) parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action)

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 509 assertion_line: 626
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -18,4 +18,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 492 assertion_line: 583
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -79,4 +79,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,5 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 582
expression: "format!(\"{:#?}\", config)" expression: "format!(\"{:#?}\", config)"
--- ---
Config { Config {
@ -975,6 +976,7 @@ Config {
): [ ): [
NewTab( NewTab(
None, None,
[],
None, None,
), ),
SwitchToMode( SwitchToMode(
@ -3049,6 +3051,7 @@ Config {
): [ ): [
NewTab( NewTab(
None, None,
[],
None, None,
), ),
SwitchToMode( SwitchToMode(

View file

@ -1,5 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 640
expression: "format!(\"{:#?}\", config)" expression: "format!(\"{:#?}\", config)"
--- ---
Config { Config {
@ -975,6 +976,7 @@ Config {
): [ ): [
NewTab( NewTab(
None, None,
[],
None, None,
), ),
SwitchToMode( SwitchToMode(
@ -3049,6 +3051,7 @@ Config {
): [ ): [
NewTab( NewTab(
None, None,
[],
None, None,
), ),
SwitchToMode( SwitchToMode(

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 492 assertion_line: 608
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -18,4 +18,5 @@ Layout {
external_children_index: None, external_children_index: None,
}, },
), ),
floating_panes_template: [],
} }

View file

@ -1,5 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 668
expression: "format!(\"{:#?}\", config)" expression: "format!(\"{:#?}\", config)"
--- ---
Config { Config {
@ -975,6 +976,7 @@ Config {
): [ ): [
NewTab( NewTab(
None, None,
[],
None, None,
), ),
SwitchToMode( SwitchToMode(
@ -3049,6 +3051,7 @@ Config {
): [ ): [
NewTab( NewTab(
None, None,
[],
None, None,
), ),
SwitchToMode( SwitchToMode(

View file

@ -1,5 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 682
expression: "format!(\"{:#?}\", config)" expression: "format!(\"{:#?}\", config)"
--- ---
Config { Config {
@ -975,6 +976,7 @@ Config {
): [ ): [
NewTab( NewTab(
None, None,
[],
None, None,
), ),
SwitchToMode( SwitchToMode(
@ -3049,6 +3051,7 @@ Config {
): [ ): [
NewTab( NewTab(
None, None,
[],
None, None,
), ),
SwitchToMode( SwitchToMode(

View file

@ -1,5 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 654
expression: "format!(\"{:#?}\", config)" expression: "format!(\"{:#?}\", config)"
--- ---
Config { Config {
@ -975,6 +976,7 @@ Config {
): [ ): [
NewTab( NewTab(
None, None,
[],
None, None,
), ),
SwitchToMode( SwitchToMode(
@ -3049,6 +3051,7 @@ Config {
): [ ): [
NewTab( NewTab(
None, None,
[],
None, None,
), ),
SwitchToMode( SwitchToMode(