Skip to content

Commit

Permalink
feat: add focus attribute in layout (#958)
Browse files Browse the repository at this point in the history
* feat(layout): add focus attribute in layout

* feat: add state of focus to tab

* chore: i love clippy

* test(layout): update focus options

* feat: add focus pane

* feat: apply focus-pane when layout is only loaded

* change the instruction name for focus-on-tab

* chore: apply cargo-fmt

* test: add e2e testcase
  • Loading branch information
jaeheonji committed Jan 31, 2022
1 parent 1163189 commit 1d2e303
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 22 deletions.
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)))
.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

0 comments on commit 1d2e303

Please sign in to comment.