diff options
author | Aram Drevekenin <aram@poor.dev> | 2022-12-24 15:48:04 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-24 15:48:04 +0100 |
commit | 799fa5de8dfd0b826be0f903796bba48a470127e (patch) | |
tree | 8c202e116b757162cadd0cc350da256b9741e248 /zellij-server | |
parent | 17205793e4cd2da5eb431dacfc87dab574080b5b (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')
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 |