summaryrefslogtreecommitdiffstats
path: root/zellij-server
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2022-12-24 15:48:04 +0100
committerGitHub <noreply@github.com>2022-12-24 15:48:04 +0100
commit799fa5de8dfd0b826be0f903796bba48a470127e (patch)
tree8c202e116b757162cadd0cc350da256b9741e248 /zellij-server
parent17205793e4cd2da5eb431dacfc87dab574080b5b (diff)
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
Diffstat (limited to 'zellij-server')
-rw-r--r--zellij-server/src/lib.rs13
-rw-r--r--zellij-server/src/panes/floating_panes/mod.rs49
-rw-r--r--zellij-server/src/panes/tiled_panes/mod.rs22
-rw-r--r--zellij-server/src/plugins/mod.rs17
-rw-r--r--zellij-server/src/pty.rs378
-rw-r--r--zellij-server/src/route.rs8
-rw-r--r--zellij-server/src/screen.rs33
-rw-r--r--zellij-server/src/tab/layout_applier.rs376
-rw-r--r--zellij-server/src/tab/mod.rs247
-rw-r--r--zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__tab_with_layout_that_has_floating_panes.snap26
-rw-r--r--zellij-server/src/tab/unit/tab_integration_tests.rs58
-rw-r--r--zellij-server/src/tab/unit/tab_tests.rs17
-rw-r--r--zellij-server/src/unit/screen_tests.rs10
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap3
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap3
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap4
-rw-r--r--zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap4
17 files changed, 872 insertions, 396 deletions
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 40c2edc2f..bae871f44 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -332,7 +332,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
})
});
- let spawn_tabs = |tab_layout, tab_name| {
+ let spawn_tabs = |tab_layout, floating_panes_layout, tab_name| {
session_data
.read()
.unwrap()
@@ -342,6 +342,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.send_to_screen(ScreenInstruction::NewTab(
default_shell.clone(),
tab_layout,
+ floating_panes_layout,
tab_name,
client_id,
))
@@ -349,8 +350,12 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
};
if layout.has_tabs() {
- for (tab_name, tab_layout) in layout.tabs() {
- spawn_tabs(Some(tab_layout.clone()), tab_name);
+ for (tab_name, tab_layout, floating_panes_layout) in layout.tabs() {
+ spawn_tabs(
+ Some(tab_layout.clone()),
+ floating_panes_layout.clone(),
+ tab_name,
+ );
}
if let Some(focused_tab_index) = layout.focused_tab_index() {
@@ -367,7 +372,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap();
}
} else {
- spawn_tabs(None, None);
+ spawn_tabs(None, layout.floating_panes_template.clone(), None);
}
session_data
.read()
diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs
index fe98ee63d..47b3e2f25 100644
--- a/zellij-server/src/panes/floating_panes/mod.rs
+++ b/zellij-server/src/panes/floating_panes/mod.rs
@@ -25,7 +25,8 @@ use zellij_utils::{
data::{ModeInfo, Style},
errors::prelude::*,
input::command::RunCommand,
- pane_size::{Offset, PaneGeom, Size, Viewport},
+ input::layout::FloatingPanesLayout,
+ pane_size::{Dimension, Offset, PaneGeom, Size, Viewport},
};
const RESIZE_INCREMENT_WIDTH: usize = 5;
@@ -224,9 +225,55 @@ impl FloatingPanes {
);
floating_pane_grid.find_room_for_new_pane()
}
+ pub fn position_floating_pane_layout(
+ &mut self,
+ floating_pane_layout: &FloatingPanesLayout,
+ ) -> PaneGeom {
+ let display_area = *self.display_area.borrow();
+ let viewport = *self.viewport.borrow();
+ let floating_pane_grid = FloatingPaneGrid::new(
+ &mut self.panes,
+ &mut self.desired_pane_positions,
+ display_area,
+ viewport,
+ );
+ let mut position = floating_pane_grid.find_room_for_new_pane().unwrap(); // TODO: no unwrap
+ if let Some(x) = &floating_pane_layout.x {
+ position.x = x.to_position(display_area.cols);
+ }
+ if let Some(y) = &floating_pane_layout.y {
+ position.y = y.to_position(display_area.rows);
+ }
+ if let Some(width) = &floating_pane_layout.width {
+ position.cols = Dimension::fixed(width.to_position(display_area.cols));
+ }
+ if let Some(height) = &floating_pane_layout.height {
+ position.rows = Dimension::fixed(height.to_position(display_area.rows));
+ }
+ if position.cols.as_usize() > display_area.cols {
+ position.cols = Dimension::fixed(display_area.cols);
+ }
+ if position.rows.as_usize() > display_area.rows {
+ position.rows = Dimension::fixed(display_area.rows);
+ }
+ if position.x + position.cols.as_usize() > display_area.cols {
+ position.x = position
+ .x
+ .saturating_sub((position.x + position.cols.as_usize()) - display_area.cols);
+ }
+ if position.y + position.rows.as_usize() > display_area.rows {
+ position.y = position
+ .y
+ .saturating_sub((position.y + position.rows.as_usize()) - display_area.rows);
+ }
+ position
+ }
pub fn first_floating_pane_id(&self) -> Option<PaneId> {
self.panes.keys().next().copied()
}
+ pub fn last_floating_pane_id(&self) -> Option<PaneId> {
+ self.panes.keys().last().copied()
+ }
pub fn first_active_floating_pane_id(&self) -> Option<PaneId> {
self.active_panes.values().next().copied()
}
diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs
index 726627f4b..cad53e3f1 100644
--- a/zellij-server/src/panes/tiled_panes/mod.rs
+++ b/zellij-server/src/panes/tiled_panes/mod.rs
@@ -539,10 +539,13 @@ impl TiledPanes {
viewport.cols = (viewport.cols as isize + column_difference) as usize;
display_area.cols = cols;
},
- Err(e) => {
- Err::<(), _>(anyError::msg(e))
- .context("failed to resize tab horizontally")
- .non_fatal();
+ Err(e) => match e.downcast_ref::<ZellijError>() {
+ Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout
+ _ => {
+ Err::<(), _>(anyError::msg(e))
+ .context("failed to resize tab horizontally")
+ .non_fatal();
+ },
},
};
match pane_grid.layout(SplitDirection::Vertical, rows) {
@@ -551,10 +554,13 @@ impl TiledPanes {
viewport.rows = (viewport.rows as isize + row_difference) as usize;
display_area.rows = rows;
},
- Err(e) => {
- Err::<(), _>(anyError::msg(e))
- .context("failed to resize tab vertically")
- .non_fatal();
+ Err(e) => match e.downcast_ref::<ZellijError>() {
+ Some(ZellijError::PaneSizeUnchanged) => {}, // ignore unchanged layout
+ _ => {
+ Err::<(), _>(anyError::msg(e))
+ .context("failed to resize tab vertically")
+ .non_fatal();
+ },
},
};
}
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index 100833ae6..ba46a5459 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -12,7 +12,7 @@ use zellij_utils::{
errors::{prelude::*, ContextType, PluginContext},
input::{
command::TerminalAction,
- layout::{Layout, PaneLayout, Run, RunPlugin, RunPluginLocation},
+ layout::{FloatingPanesLayout, Layout, PaneLayout, Run, RunPlugin, RunPluginLocation},
plugins::PluginsConfig,
},
pane_size::Size,
@@ -29,6 +29,7 @@ pub enum PluginInstruction {
NewTab(
Option<TerminalAction>,
Option<PaneLayout>,
+ Vec<FloatingPanesLayout>,
Option<String>, // tab name
usize, // tab_index
ClientId,
@@ -69,7 +70,6 @@ pub(crate) fn plugin_thread_main(
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin((&event).into()));
match event {
- // TODO: remove pid_tx from here
PluginInstruction::Load(run, tab_index, client_id, size) => {
wasm_bridge.load_plugin(&run, tab_index, size, client_id)?;
},
@@ -91,16 +91,22 @@ pub(crate) fn plugin_thread_main(
PluginInstruction::NewTab(
terminal_action,
tab_layout,
+ floating_panes_layout,
tab_name,
tab_index,
client_id,
) => {
let mut plugin_ids: HashMap<RunPluginLocation, Vec<u32>> = HashMap::new();
- let extracted_run_instructions = tab_layout
+ let mut extracted_run_instructions = tab_layout
.clone()
- .unwrap_or_else(|| layout.new_tab())
+ .unwrap_or_else(|| layout.new_tab().0)
.extract_run_instructions();
- let size = Size::default(); // TODO: is this bad?
+ let size = Size::default();
+ let mut extracted_floating_plugins: Vec<Option<Run>> = floating_panes_layout
+ .iter()
+ .map(|f| f.run.clone())
+ .collect();
+ extracted_run_instructions.append(&mut extracted_floating_plugins);
for run_instruction in extracted_run_instructions {
if let Some(Run::Plugin(run)) = run_instruction {
let plugin_id =
@@ -111,6 +117,7 @@ pub(crate) fn plugin_thread_main(
drop(bus.senders.send_to_pty(PtyInstruction::NewTab(
terminal_action,
tab_layout,
+ floating_panes_layout,
tab_name,
tab_index,
plugin_ids,
diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs
index 9f4cd3e35..04e3391f3 100644
--- a/zellij-server/src/pty.rs
+++ b/zellij-server/src/pty.rs
@@ -15,7 +15,7 @@ use zellij_utils::{
errors::{ContextType, PtyContext},
input::{
command::{RunCommand, TerminalAction},
- layout::{Layout, PaneLayout, Run, RunPluginLocation},
+ layout::{FloatingPanesLayout, Layout, PaneLayout, Run, RunPluginLocation},
},
};
@@ -50,6 +50,7 @@ pub enum PtyInstruction {
NewTab(
Option<TerminalAction>,
Option<PaneLayout>,
+ Vec<FloatingPanesLayout>,
Option<String>,
usize, // tab_index
HashMap<RunPluginLocation, Vec<u32>>, // plugin_ids
@@ -335,6 +336,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
PtyInstruction::NewTab(
terminal_action,
tab_layout,
+ floating_panes_layout,
tab_name,
tab_index,
plugin_ids,
@@ -342,8 +344,14 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
) => {
let err_context = || format!("failed to open new tab for client {}", client_id);
+ let floating_panes_layout = if floating_panes_layout.is_empty() {
+ layout.new_tab().1
+ } else {
+ floating_panes_layout
+ };
pty.spawn_terminals_for_layout(
- tab_layout.unwrap_or_else(|| layout.new_tab()),
+ tab_layout.unwrap_or_else(|| layout.new_tab().0),
+ floating_panes_layout,
terminal_action.clone(),
plugin_ids,
tab_index,
@@ -562,6 +570,7 @@ impl Pty {
pub fn spawn_terminals_for_layout(
&mut self,
layout: PaneLayout,
+ floating_panes_layout: Vec<FloatingPanesLayout>,
default_shell: Option<TerminalAction>,
plugin_ids: HashMap<RunPluginLocation, Vec<u32>>,
tab_index: usize,
@@ -572,176 +581,28 @@ impl Pty {
let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal(None));
self.fill_cwd(&mut default_shell, client_id);
let extracted_run_instructions = layout.extract_run_instructions();
+ let extracted_floating_run_instructions =
+ floating_panes_layout.iter().map(|f| f.run.clone());
let mut new_pane_pids: Vec<(u32, bool, Option<RunCommand>, Result<RawFd>)> = vec![]; // (terminal_id,
// starts_held,
// run_command,
// file_descriptor)
+ let mut new_floating_panes_pids: Vec<(u32, bool, Option<RunCommand>, Result<RawFd>)> =
+ vec![]; // same
+ // as
+ // new_pane_pids
for run_instruction in extracted_run_instructions {
- let quit_cb = Box::new({
- let senders = self.bus.senders.clone();
- move |pane_id, _exit_status, _command| {
- let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
- }
- });
- match run_instruction {
- Some(Run::Command(command)) => {
- let starts_held = command.hold_on_start;
- let hold_on_close = command.hold_on_close;
- let quit_cb = Box::new({
- let senders = self.bus.senders.clone();
- move |pane_id, exit_status, command| {
- if hold_on_close {
- let _ = senders.send_to_screen(ScreenInstruction::HoldPane(
- pane_id,
- exit_status,
- command,
- None,
- ));
- } else {
- let _ = senders
- .send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
- }
- }
- });
- let cmd = TerminalAction::RunCommand(command.clone());
- if starts_held {
- // we don't actually open a terminal in this case, just wait for the user to run it
- match self
- .bus
- .os_input
- .as_mut()
- .context("no OS I/O interface found")
- .with_context(err_context)?
- .reserve_terminal_id()
- {
- Ok(terminal_id) => {
- new_pane_pids.push((
- terminal_id,
- starts_held,
- Some(command.clone()),
- Ok(terminal_id as i32), // this is not actually correct but gets
- // stripped later
- ));
- },
- Err(e) => Err::<(), _>(e).with_context(err_context).non_fatal(),
- }
- } else {
- match self
- .bus
- .os_input
- .as_mut()
- .context("no OS I/O interface found")
- .with_context(err_context)?
- .spawn_terminal(cmd, quit_cb, self.default_editor.clone())
- .with_context(err_context)
- {
- Ok((terminal_id, pid_primary, child_fd)) => {
- self.id_to_child_pid.insert(terminal_id, child_fd);
- new_pane_pids.push((
- terminal_id,
- starts_held,
- Some(command.clone()),
- Ok(pid_primary),
- ));
- },
- Err(err) => match err.downcast_ref::<ZellijError>() {
- Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
- new_pane_pids.push((
- *terminal_id,
- starts_held,
- Some(command.clone()),
- Err(err),
- ));
- },
- _ => {
- Err::<(), _>(err).non_fatal();
- },
- },
- }
- }
- },
- Some(Run::Cwd(cwd)) => {
- let starts_held = false; // we do not hold Cwd panes
- let shell = self.get_default_terminal(Some(cwd));
- match self
- .bus
- .os_input
- .as_mut()
- .context("no OS I/O interface found")
- .with_context(err_context)?
- .spawn_terminal(shell, quit_cb, self.default_editor.clone())
- .with_context(err_context)
- {
- Ok((terminal_id, pid_primary, child_fd)) => {
- self.id_to_child_pid.insert(terminal_id, child_fd);
- new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
- },
- Err(err) => match err.downcast_ref::<ZellijError>() {
- Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
- new_pane_pids.push((*terminal_id, starts_held, None, Err(err)));
- },
- _ => {
- Err::<(), _>(err).non_fatal();
- },
- },
- }
- },
- Some(Run::EditFile(path_to_file, line_number)) => {
- let starts_held = false; // we do not hold edit panes (for now?)
- match self
- .bus
- .os_input
- .as_mut()
- .context("no OS I/O interface found")
- .with_context(err_context)?
- .spawn_terminal(
- TerminalAction::OpenFile(path_to_file, line_number),
- quit_cb,
- self.default_editor.clone(),
- )
- .with_context(err_context)
- {
- Ok((terminal_id, pid_primary, child_fd)) => {
- self.id_to_child_pid.insert(terminal_id, child_fd);
- new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
- },
- Err(err) => match err.downcast_ref::<ZellijError>() {
- Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
- new_pane_pids.push((*terminal_id, starts_held, None, Err(err)));
- },
- _ => {
- Err::<(), _>(err).non_fatal();
- },
- },
- }
- },
- None => {
- let starts_held = false;
- match self
- .bus
- .os_input
- .as_mut()
- .context("no OS I/O interface found")
- .with_context(err_context)?
- .spawn_terminal(default_shell.clone(), quit_cb, self.default_editor.clone())
- .with_context(err_context)
- {
- Ok((terminal_id, pid_primary, child_fd)) => {
- self.id_to_child_pid.insert(terminal_id, child_fd);
- new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
- },
- Err(err) => match err.downcast_ref::<ZellijError>() {
- Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
- new_pane_pids.push((*terminal_id, starts_held, None, Err(err)));
- },
- _ => {
- Err::<(), _>(err).non_fatal();
- },
- },
- }
- },
- // Investigate moving plugin loading to here.
- Some(Run::Plugin(_)) => {},
+ if let Some(new_pane_data) =
+ self.apply_run_instruction(run_instruction, default_shell.clone())?
+ {
+ new_pane_pids.push(new_pane_data);
+ }
+ }
+ for run_instruction in extracted_floating_run_instructions {
+ if let Some(new_pane_data) =
+ self.apply_run_instruction(run_instruction, default_shell.clone())?
+ {
+ new_floating_panes_pids.push(new_pane_data);
}
}
// Option<RunCommand> should only be Some if the pane starts held
@@ -755,17 +616,32 @@ impl Pty {
}
})
.collect();
+ let new_tab_floating_pane_ids: Vec<(u32, Option<RunCommand>)> = new_floating_panes_pids
+ .iter()
+ .map(|(terminal_id, starts_held, run_command, _)| {
+ if *starts_held {
+ (*terminal_id, run_command.clone())
+ } else {
+ (*terminal_id, None)
+ }
+ })
+ .collect();
self.bus
.senders
.send_to_screen(ScreenInstruction::ApplyLayout(
layout,
+ floating_panes_layout,
new_tab_pane_ids,
+ new_tab_floating_pane_ids,
plugin_ids,
tab_index,
client_id,
))
.with_context(err_context)?;
- for (terminal_id, starts_held, run_command, pid_primary) in new_pane_pids {
+ let mut terminals_to_start = vec![];
+ terminals_to_start.append(&mut new_pane_pids);
+ terminals_to_start.append(&mut new_floating_panes_pids);
+ for (terminal_id, starts_held, run_command, pid_primary) in terminals_to_start {
if starts_held {
// we do not run a command or start listening for bytes on held panes
continue;
@@ -820,6 +696,172 @@ impl Pty {
}
Ok(())
}
+ fn apply_run_instruction(
+ &mut self,
+ run_instruction: Option<Run>,
+ default_shell: TerminalAction,
+ ) -> Result<Option<(u32, bool, Option<RunCommand>, Result<i32>)>> {
+ // terminal_id,
+ // starts_held,
+ // command
+ // successfully opened
+ let err_context = || format!("failed to apply run instruction");
+ let quit_cb = Box::new({
+ let senders = self.bus.senders.clone();
+ move |pane_id, _exit_status, _command| {
+ let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
+ }
+ });
+ match run_instruction {
+ Some(Run::Command(command)) => {
+ let starts_held = command.hold_on_start;
+ let hold_on_close = command.hold_on_close;
+ let quit_c