Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add focus attribute in layout #958

Merged
merged 11 commits into from
Jan 31, 2022
37 changes: 37 additions & 0 deletions src/tests/e2e/cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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.
92 changes: 92 additions & 0 deletions src/tests/fixtures/layouts/focus-tab-layout.yaml
Original file line number Diff line number Diff line change
@@ -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
18 changes: 17 additions & 1 deletion zellij-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
9 changes: 9 additions & 0 deletions zellij-server/src/pty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use zellij_utils::{
};

pub type VteBytes = Vec<u8>;
pub type TabIndex = u32;

#[derive(Clone, Copy, Debug)]
pub enum ClientOrTabIndex {
Expand All @@ -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>),
Expand All @@ -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,
Expand Down Expand Up @@ -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)))
a-kenji marked this conversation as resolved.
Show resolved Hide resolved
.unwrap();
}
PtyInstruction::NewTab(terminal_action, tab_layout, client_id) => {
let tab_name = tab_layout.as_ref().and_then(|layout| {
if layout.name.is_empty() {
Expand Down
69 changes: 48 additions & 21 deletions zellij-server/src/tab/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -340,6 +343,7 @@ impl Tab {
connected_clients,
selecting_with_mouse: false,
copy_command,
focus_pane_id: None,
}
}

Expand Down Expand Up @@ -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() {
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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();
}
}
}
}
Expand Down Expand Up @@ -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()),
Expand All @@ -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();
}
Expand Down
1 change: 1 addition & 0 deletions zellij-utils/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ pub enum PtyContext {
SpawnTerminalVertically,
SpawnTerminalHorizontally,
UpdateActivePane,
GoToTab,
NewTab,
ClosePane,
CloseTab,
Expand Down
Loading