summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/tests/e2e/cases.rs37
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap29
-rw-r--r--src/tests/fixtures/layouts/focus-tab-layout.yaml92
-rw-r--r--zellij-server/src/lib.rs18
-rw-r--r--zellij-server/src/pty.rs9
-rw-r--r--zellij-server/src/tab/mod.rs69
-rw-r--r--zellij-utils/src/errors.rs1
-rw-r--r--zellij-utils/src/input/layout.rs9
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs58
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