diff options
-rw-r--r-- | src/tests/e2e/cases.rs | 37 | ||||
-rw-r--r-- | src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap | 29 | ||||
-rw-r--r-- | src/tests/fixtures/layouts/focus-tab-layout.yaml | 92 | ||||
-rw-r--r-- | zellij-server/src/lib.rs | 18 | ||||
-rw-r--r-- | zellij-server/src/pty.rs | 9 | ||||
-rw-r--r-- | zellij-server/src/tab/mod.rs | 69 | ||||
-rw-r--r-- | zellij-utils/src/errors.rs | 1 | ||||
-rw-r--r-- | zellij-utils/src/input/layout.rs | 9 | ||||
-rw-r--r-- | zellij-utils/src/input/unit/layout_test.rs | 58 |
9 files changed, 300 insertions, 22 deletions
diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index 3fb0d44d9..807d71e80 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -1663,3 +1663,40 @@ pub fn bracketed_paste() { }; assert_snapshot!(last_snapshot); } + +#[test] +#[ignore] +pub fn focus_tab_with_layout() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + let layout_file_name = "focus-tab-layout.yaml"; + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + let mut runner = RemoteRunner::new_with_layout(fake_win_size, layout_file_name); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for app to load", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.status_bar_appears() + && remote_terminal.tip_appears() + && remote_terminal.snapshot_contains("Tab #3") + && remote_terminal.cursor_position_is(63, 2) + { + step_is_complete = true; + } + step_is_complete + }, + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + assert_snapshot!(last_snapshot); +} diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap new file mode 100644 index 000000000..fc4ff6d43 --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap @@ -0,0 +1,29 @@ +--- +source: src/tests/e2e/cases.rs +expression: last_snapshot + +--- + Zellij (e2e-test) Tab #1 Tab #2 Tab #3 Tab #4 Tab #5 Tab #6 Tab #7 Tab #8 Tab #9 +┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐ +│$ ││$ █ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ + Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SCROLL <o> SESSION <q> QUIT + Tip: Alt + <n> => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/fixtures/layouts/focus-tab-layout.yaml b/src/tests/fixtures/layouts/focus-tab-layout.yaml new file mode 100644 index 000000000..1d0190204 --- /dev/null +++ b/src/tests/fixtures/layouts/focus-tab-layout.yaml @@ -0,0 +1,92 @@ +--- +template: + direction: Horizontal + parts: + - direction: Vertical + split_size: + Fixed: 1 + run: + plugin: + location: "zellij:tab-bar" + borderless: true + - direction: Vertical + body: true + - direction: Vertical + split_size: + Fixed: 2 + run: + plugin: + location: "zellij:status-bar" + borderless: true + +tabs: +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical +- direction: Vertical + focus: true + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + focus: true + split_size: + Percent: 50 +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Horizontal + split_size: + Percent: 50 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical +- direction: Vertical +- direction: Vertical +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 20 + run: + plugin: + location: "zellij:strider" + - direction: Horizontal + split_size: + Percent: 80 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 +- direction: Vertical + parts: + - direction: Vertical + split_size: + Percent: 40 + - direction: Horizontal + split_size: + Percent: 60 + parts: + - direction: Vertical + split_size: + Percent: 50 + - direction: Vertical + split_size: + Percent: 50 diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 96b7e7fa0..3f7ac9898 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -313,9 +313,25 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) { }; if !&layout.tabs.is_empty() { - for tab_layout in layout.tabs { + for tab_layout in layout.clone().tabs { spawn_tabs(Some(tab_layout.clone())); } + + let focused_tab = layout + .tabs + .into_iter() + .enumerate() + .find(|(_, tab_layout)| tab_layout.focus.unwrap_or(false)); + if let Some((tab_index, _)) = focused_tab { + session_data + .read() + .unwrap() + .as_ref() + .unwrap() + .senders + .send_to_pty(PtyInstruction::GoToTab((tab_index + 1) as u32, client_id)) + .unwrap(); + } } else { spawn_tabs(None); } diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 99f7e3756..327347cb5 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -29,6 +29,7 @@ use zellij_utils::{ }; pub type VteBytes = Vec<u8>; +pub type TabIndex = u32; #[derive(Clone, Copy, Debug)] pub enum ClientOrTabIndex { @@ -43,6 +44,7 @@ pub(crate) enum PtyInstruction { SpawnTerminalVertically(Option<TerminalAction>, ClientId), SpawnTerminalHorizontally(Option<TerminalAction>, ClientId), UpdateActivePane(Option<PaneId>, ClientId), + GoToTab(TabIndex, ClientId), NewTab(Option<TerminalAction>, Option<TabLayout>, ClientId), ClosePane(PaneId), CloseTab(Vec<PaneId>), @@ -56,6 +58,7 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::SpawnTerminalVertically(..) => PtyContext::SpawnTerminalVertically, PtyInstruction::SpawnTerminalHorizontally(..) => PtyContext::SpawnTerminalHorizontally, PtyInstruction::UpdateActivePane(..) => PtyContext::UpdateActivePane, + PtyInstruction::GoToTab(..) => PtyContext::GoToTab, PtyInstruction::ClosePane(_) => PtyContext::ClosePane, PtyInstruction::CloseTab(_) => PtyContext::CloseTab, PtyInstruction::NewTab(..) => PtyContext::NewTab, @@ -114,6 +117,12 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<LayoutFromYaml>) { PtyInstruction::UpdateActivePane(pane_id, client_id) => { pty.set_active_pane(pane_id, client_id); } + PtyInstruction::GoToTab(tab_index, client_id) => { + pty.bus + .senders + .send_to_screen(ScreenInstruction::GoToTab(tab_index, Some(client_id))) + .unwrap(); + } PtyInstruction::NewTab(terminal_action, tab_layout, client_id) => { let tab_name = tab_layout.as_ref().and_then(|layout| { if layout.name.is_empty() { diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 98871c63a..66533aa3f 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -122,6 +122,9 @@ pub(crate) struct Tab { pending_vte_events: HashMap<RawFd, Vec<VteBytes>>, selecting_with_mouse: bool, copy_command: Option<String>, + // TODO: used only to focus the pane when the layout is loaded + // it seems that optimization is possible using `active_panes` + focus_pane_id: Option<PaneId>, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -340,6 +343,7 @@ impl Tab { connected_clients, selecting_with_mouse: false, copy_command, + focus_pane_id: None, } } @@ -374,6 +378,13 @@ impl Tab { } let mut new_pids = new_pids.iter(); + let mut focus_pane_id: Option<PaneId> = None; + let mut set_focus_pane_id = |layout: &Layout, 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() { @@ -392,6 +403,7 @@ impl Tab { ); new_plugin.set_borderless(layout.borderless); self.panes.insert(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 let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout @@ -406,6 +418,7 @@ impl Tab { new_pane.set_borderless(layout.borderless); self.panes .insert(PaneId::Terminal(*pid), Box::new(new_pane)); + set_focus_pane_id(layout, PaneId::Terminal(*pid)); } } for unused_pid in new_pids { @@ -435,24 +448,33 @@ impl Tab { self.offset_viewport(&geom) } self.set_pane_frames(self.draw_pane_frames); - // This is the end of the nasty viewport hack... - let next_selectable_pane_id = self - .panes - .iter() - .filter(|(_id, pane)| pane.selectable()) - .map(|(id, _)| id.to_owned()) - .next(); - match next_selectable_pane_id { - Some(active_pane_id) => { - let connected_clients: Vec<ClientId> = - self.connected_clients.iter().copied().collect(); - for client_id in connected_clients { - self.active_panes.insert(client_id, active_pane_id); - } + + let mut active_pane = |pane_id: PaneId| { + let connected_clients: Vec<ClientId> = self.connected_clients.iter().copied().collect(); + for client_id in connected_clients { + self.active_panes.insert(client_id, pane_id); } - None => { - // this is very likely a configuration error (layout with no selectable panes) - self.active_panes.clear(); + }; + + if let Some(pane_id) = focus_pane_id { + self.focus_pane_id = Some(pane_id); + active_pane(pane_id); + } else { + // This is the end of the nasty viewport hack... + let next_selectable_pane_id = self + .panes + .iter() + .filter(|(_id, pane)| pane.selectable()) + .map(|(id, _)| id.to_owned()) + .next(); + match next_selectable_pane_id { + Some(active_pane_id) => { + active_pane(active_pane_id); + } + None => { + // this is very likely a configuration error (layout with no selectable panes) + self.active_panes.clear(); + } } } } @@ -489,11 +511,15 @@ impl Tab { // no panes here, bye bye return; } - pane_ids.sort(); // TODO: make this predictable - pane_ids.retain(|p| !self.panes_to_hide.contains(p)); - let first_pane_id = pane_ids.get(0).unwrap(); + self.active_panes.insert( + client_id, + self.focus_pane_id.unwrap_or_else(|| { + pane_ids.sort(); // TODO: make this predictable + pane_ids.retain(|p| !self.panes_to_hide.contains(p)); + *pane_ids.get(0).unwrap() + }), + ); self.connected_clients.insert(client_id); - self.active_panes.insert(client_id, *first_pane_id); self.mode_info.insert( client_id, mode_info.unwrap_or_else(|| self.default_mode_info.clone()), @@ -515,6 +541,7 @@ impl Tab { } } pub fn remove_client(&mut self, client_id: ClientId) { + self.focus_pane_id = None; self.connected_clients.remove(&client_id); self.set_force_render(); } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index a546e6cc7..884103894 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -281,6 +281,7 @@ pub enum PtyContext { SpawnTerminalVertically, SpawnTerminalHorizontally, UpdateActivePane, + GoToTab, NewTab, ClosePane, CloseTab, diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 9fa6709c2..d2125096e 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -140,6 +140,7 @@ pub struct Layout { pub run: Option<Run>, #[serde(default)] pub borderless: bool, + pub focus: Option<bool>, } // The struct that is used to deserialize the layout from @@ -421,6 +422,7 @@ pub struct LayoutTemplate { #[serde(default)] pub body: bool, pub split_size: Option<SplitSize>, + pub focus: Option<bool>, pub run: Option<RunFromYaml>, } @@ -466,6 +468,7 @@ pub struct TabLayout { pub split_size: Option<SplitSize>, #[serde(default)] pub name: String, + pub focus: Option<bool>, pub run: Option<RunFromYaml>, } @@ -712,6 +715,7 @@ impl TryFrom<TabLayout> for Layout { borderless: tab.borderless, parts: Self::from_vec_tab_layout(tab.parts)?, split_size: tab.split_size, + focus: tab.focus, run: tab.run.map(Run::try_from).transpose()?, }) } @@ -726,6 +730,7 @@ impl From<TabLayout> for LayoutTemplate { parts: Self::from_vec_tab_layout(tab.parts), body: false, split_size: tab.split_size, + focus: tab.focus, run: tab.run, } } @@ -741,6 +746,7 @@ impl TryFrom<LayoutTemplate> for Layout { borderless: template.borderless, parts: Self::from_vec_template_layout(template.parts)?, split_size: template.split_size, + focus: template.focus, run: template .run .map(Run::try_from) @@ -761,6 +767,7 @@ impl Default for TabLayout { run: None, name: String::new(), pane_name: None, + focus: None, } } } @@ -778,10 +785,12 @@ impl Default for LayoutTemplate { body: true, borderless: false, split_size: None, + focus: None, run: None, parts: vec![], }], split_size: None, + focus: None, run: None, } } diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index aec34c458..292b6b01d 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -41,11 +41,13 @@ fn default_layout_merged_correctly() { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![ Layout { direction: Direction::Vertical, borderless: true, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Fixed(1)), run: Some(Run::Plugin(RunPlugin { @@ -57,6 +59,7 @@ fn default_layout_merged_correctly() { direction: Direction::Vertical, borderless: false, pane_name: None, + focus: None, parts: vec![], split_size: None, run: None, @@ -65,6 +68,7 @@ fn default_layout_merged_correctly() { direction: Direction::Vertical, borderless: true, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Fixed(2)), run: Some(Run::Plugin(RunPlugin { @@ -89,11 +93,13 @@ fn default_layout_new_tab_correct() { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![ Layout { direction: Direction::Vertical, borderless: true, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Fixed(1)), run: Some(Run::Plugin(RunPlugin { @@ -105,6 +111,7 @@ fn default_layout_new_tab_correct() { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![], split_size: None, run: None, @@ -113,6 +120,7 @@ fn default_layout_new_tab_correct() { direction: Direction::Vertical, borderless: true, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Fixed(2)), run: Some(Run::Plugin(RunPlugin { @@ -177,15 +185,18 @@ fn three_panes_with_tab_merged_correctly() { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![Layout { direction: Direction::Vertical, borderless: false, pane_name: None, + focus: None, parts: vec![ Layout { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Percent(50.0)), run: None, @@ -194,11 +205,13 @@ fn three_panes_with_tab_merged_correctly() { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![ Layout { direction: Direction::Vertical, borderless: false, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Percent(50.0)), run: None, @@ -207,6 +220,7 @@ fn three_panes_with_tab_merged_correctly() { direction: Direction::Vertical, borderless: false, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Percent(50.0)), run: None, @@ -235,10 +249,12 @@ fn three_panes_with_tab_new_tab_is_correct() { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![Layout { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![], split_size: None, run: None, @@ -277,11 +293,13 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![ Layout { direction: Direction::Vertical, borderless: false, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Fixed(1)), run: Some(Run::Plugin(RunPlugin { @@ -293,11 +311,13 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { direction: Direction::Vertical, borderless: false, pane_name: None, + focus: None, parts: vec![ Layout { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Percent(50.0)), run: None, @@ -306,11 +326,13 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { direction: Direction::Horizontal, borderless: false, pane_name: None, + focus: None, parts: vec![ Layout { direction: Direction::Vertical, borderless: false, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Percent(50.0)), run: None, @@ -319,6 +341,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { direction: Direction::Vertical, borderless: false, pane_name: None, + focus: None, parts: vec![], split_size: Some(SplitSize::Percent(50.0)), run: None |