Skip to content

feat(panes): FocusLastPane action #4241

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions default-plugins/fixture-plugin-for-tests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ impl ZellijPlugin for State {
BareKey::Char('z') if key.has_modifiers(&[KeyModifier::Alt]) => {
list_clients();
},
BareKey::Char('0') if key.has_modifiers(&[KeyModifier::Alt]) => focus_last_pane(),
_ => {},
},
Event::CustomMessage(message, payload) => {
Expand Down
1 change: 1 addition & 0 deletions docs/MANPAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ ACTIONS
on screen edge.
* __FocusPreviousPane__ - switches focus to the next pane to the left or above
if on screen edge.
* __FocusLastPane__ - switches focus to the previously focused pane.
* __SwitchFocus__ - left for legacy support. Switches focus to a pane with the
next ID.
* __MoveFocus: <Direction\>__ - moves focus in the specified direction (Left,
Expand Down
1 change: 1 addition & 0 deletions example/alt-centered-config.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ keybinds {
bind "Alt k" { MoveFocus "Up"; }
bind "Alt +" { Resize "Increase"; }
bind "Alt -" { Resize "Decrease"; }
bind "Alt \\" { FocusLastPane; }
}
pane clear-defaults=true {
bind "Enter" "Esc" "Space" { SwitchToMode "normal"; }
Expand Down
2 changes: 2 additions & 0 deletions example/config.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ keybinds {
bind "l" "Right" { MoveFocus "Right"; }
bind "j" "Down" { MoveFocus "Down"; }
bind "k" "Up" { MoveFocus "Up"; }
bind "\\" { FocusLastPane; }
bind "p" { SwitchFocus; }
bind "n" { NewPane; SwitchToMode "Normal"; }
bind "d" { NewPane "Down"; SwitchToMode "Normal"; }
Expand Down Expand Up @@ -125,6 +126,7 @@ keybinds {
bind "j" { MoveFocus "Down"; SwitchToMode "Normal"; }
bind "k" { MoveFocus "Up"; SwitchToMode "Normal"; }
bind "o" { FocusNextPane; }
bind ";" { FocusLastPane; }
bind "d" { Detach; }
bind "x" { CloseFocus; SwitchToMode "Normal"; }
}
Expand Down
2 changes: 2 additions & 0 deletions example/default.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ keybinds {
bind "j" { MoveFocus "Down"; SwitchToMode "Normal"; }
bind "k" { MoveFocus "Up"; SwitchToMode "Normal"; }
bind "o" { FocusNextPane; }
bind ";" { FocusLastPane; }
bind "d" { Detach; }
bind "Space" { NextSwapLayout; }
bind "x" { CloseFocus; SwitchToMode "Normal"; }
Expand All @@ -188,6 +189,7 @@ keybinds {
bind "Alt -" { Resize "Decrease"; }
bind "Alt [" { PreviousSwapLayout; }
bind "Alt ]" { NextSwapLayout; }
bind "Alt \\" { FocusLastPane; }
}
shared_except "normal" "locked" {
bind "Enter" "Esc" { SwitchToMode "Normal"; }
Expand Down
2 changes: 2 additions & 0 deletions zellij-client/src/old_config_converter/old_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,7 @@ enum OldAction {
Resize(OldResizeDirection),
FocusNextPane,
FocusPreviousPane,
FocusLastPane,
SwitchFocus,
MoveFocus(OldDirection),
MoveFocusOrTab(OldDirection),
Expand Down Expand Up @@ -1068,6 +1069,7 @@ impl std::fmt::Display for OldAction {
Self::Resize(resize_direction) => write!(f, "Resize \"{}\"", resize_direction),
Self::FocusNextPane => write!(f, "FocusNextPane"),
Self::FocusPreviousPane => write!(f, "FocusPreviousPane"),
Self::FocusLastPane => write!(f, "FocusLastPane"),
Self::SwitchFocus => write!(f, "SwitchFocus"),
Self::MoveFocus(direction) => write!(f, "MoveFocus \"{}\"", direction),
Self::MoveFocusOrTab(direction) => write!(f, "MoveFocusOrTab \"{}\"", direction),
Expand Down
12 changes: 12 additions & 0 deletions zellij-server/src/panes/active_panes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::collections::{BTreeMap, HashMap};
#[derive(Clone)]
pub struct ActivePanes {
active_panes: HashMap<ClientId, PaneId>,
last_panes: HashMap<ClientId, PaneId>,
os_api: Box<dyn ServerOsApi>,
}

Expand All @@ -20,12 +21,16 @@ impl ActivePanes {
let os_api = os_api.clone();
ActivePanes {
active_panes: HashMap::new(),
last_panes: HashMap::new(),
os_api,
}
}
pub fn get(&self, client_id: &ClientId) -> Option<&PaneId> {
self.active_panes.get(client_id)
}
pub fn get_last(&self, client_id: &ClientId) -> Option<&PaneId> {
self.last_panes.get(client_id)
}
pub fn insert(
&mut self,
client_id: ClientId,
Expand All @@ -36,6 +41,13 @@ impl ActivePanes {
self.active_panes.insert(client_id, pane_id);
self.focus_pane(pane_id, panes);
}
pub fn set_last_pane(
&mut self,
client_id: ClientId,
pane_id: PaneId,
) {
self.last_panes.insert(client_id, pane_id);
}
pub fn clear(&mut self, panes: &mut BTreeMap<PaneId, Box<dyn Pane>>) {
for pane_id in self.active_panes.values() {
self.unfocus_pane(*pane_id, panes);
Expand Down
12 changes: 12 additions & 0 deletions zellij-server/src/panes/floating_panes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,13 @@ impl FloatingPanes {
}
}

pub fn focus_last_pane(&mut self, client_id: ClientId) {
if let Some(pane_id) = self.active_panes.get_last(&client_id).copied() {
self.focus_pane(pane_id, client_id);
self.set_force_render();
}
}

pub fn move_active_pane_down(&mut self, client_id: ClientId) {
if let Some(active_pane_id) = self.active_panes.get(&client_id) {
self.move_pane_down(*active_pane_id);
Expand Down Expand Up @@ -829,6 +836,11 @@ impl FloatingPanes {
self.set_force_render();
}
pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
if let Some(focused_pane) = self.active_panes.get(&client_id) {
if pane_id != *focused_pane {
self.active_panes.set_last_pane(client_id, *focused_pane);
}
}
let pane_is_selectable = self
.panes
.get(&pane_id)
Expand Down
47 changes: 47 additions & 0 deletions zellij-server/src/panes/tiled_panes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,11 @@ impl TiledPanes {
}
}
pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
if let Some(focused_pane) = self.active_panes.get(&client_id).copied() {
if pane_id != focused_pane {
self.active_panes.set_last_pane(client_id, focused_pane);
}
}
let pane_is_selectable = self
.panes
.get(&pane_id)
Expand Down Expand Up @@ -970,6 +975,9 @@ impl TiledPanes {
pub fn get_active_pane_id(&self, client_id: ClientId) -> Option<PaneId> {
self.active_panes.get(&client_id).copied()
}
pub fn get_last_pane_id(&self, client_id: ClientId) -> Option<PaneId> {
self.active_panes.get_last(&client_id).copied()
}
pub fn panes_contain(&self, pane_id: &PaneId) -> bool {
self.panes.contains_key(pane_id)
}
Expand Down Expand Up @@ -1747,6 +1755,39 @@ impl TiledPanes {
self.set_pane_active_at(next_active_pane_id);
self.reset_boundaries();
}
pub fn focus_last_pane(&mut self, client_id: ClientId) {
let Some(last_pane_id) = self.get_last_pane_id(client_id) else {
return;
};

let previously_active_pane_id = self.active_panes.get(&client_id).unwrap();
let previously_active_pane = self
.panes
.get_mut(previously_active_pane_id)
.unwrap();

previously_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor)
previously_active_pane.render_full_viewport();

let next_active_pane = self.panes.get_mut(&last_pane_id).unwrap();
let stacked = next_active_pane.current_geom().stacked;
next_active_pane.set_should_render(true);
// we render the full viewport to remove any ui elements that might have been
// there before (eg. another user's cursor)
next_active_pane.render_full_viewport();

self.focus_pane(last_pane_id, client_id);
self.set_pane_active_at(last_pane_id);
if let Some(stack_id) = stacked {
// we do this because a stack pane focus change also changes its
// geometry and we need to let the pty know about this (like in a
// normal size change)
self.focus_pane_for_all_clients_in_stack(last_pane_id, stack_id);
self.reapply_pane_frames();
}
}
fn set_pane_active_at(&mut self, pane_id: PaneId) {
if let Some(pane) = self.get_pane_mut(pane_id) {
pane.set_active_at(Instant::now());
Expand Down Expand Up @@ -2513,6 +2554,12 @@ impl TiledPanes {
self.toggle_active_pane_fullscreen(client_id);
}

pub fn switch_last_pane_fullscreen(&mut self, client_id: ClientId) {
self.unset_fullscreen();
self.focus_last_pane(client_id);
self.toggle_active_pane_fullscreen(client_id);
}

pub fn panes_to_hide_count(&self) -> usize {
self.panes_to_hide.len()
}
Expand Down
73 changes: 73 additions & 0 deletions zellij-server/src/plugins/unit/plugin_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1829,6 +1829,79 @@ pub fn focus_previous_pane_plugin_command() {
assert_snapshot!(format!("{:#?}", new_tab_event));
}

#[test]
#[ignore]
pub fn focus_last_pane_plugin_command() {
let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
// destructor removes the directory
let plugin_host_folder = PathBuf::from(temp_folder.path());
let cache_path = plugin_host_folder.join("permissions_test.kdl");
let (plugin_thread_sender, screen_receiver, teardown) =
create_plugin_thread(Some(plugin_host_folder));
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
..Default::default()
});
let tab_index = 1;
let client_id = 1;
let size = Size {
cols: 121,
rows: 20,
};
let received_screen_instructions = Arc::new(Mutex::new(vec![]));
let screen_thread = grant_permissions_and_log_actions_in_thread!(
received_screen_instructions,
ScreenInstruction::FocusLastPane,
screen_receiver,
1,
&PermissionType::ChangeApplicationState,
cache_path,
plugin_thread_sender,
client_id
);

let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
let _ = plugin_thread_sender.send(PluginInstruction::Load(
plugin_should_float,
false,
plugin_title,
run_plugin,
Some(tab_index),
None,
client_id,
size,
None,
false,
None,
None,
));
std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
Event::Key(KeyWithModifier::new(BareKey::Char('0')).with_alt_modifier()), // this triggers the enent in the fixture plugin
)]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown();
let new_tab_event = received_screen_instructions
.lock()
.unwrap()
.iter()
.find_map(|i| {
if let ScreenInstruction::FocusLastPane(..) = i {
Some(i.clone())
} else {
None
}
})
.clone();
assert_snapshot!(format!("{:#?}", new_tab_event));
}

#[test]
#[ignore]
pub fn move_focus_plugin_command() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
expression: "format!(\"{:#?}\", new_tab_event)"
---
Some(
FocusLastPane(
1,
),
)
7 changes: 7 additions & 0 deletions zellij-server/src/plugins/zellij_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ fn host_run_plugin_command(mut caller: Caller<'_, PluginEnv>) {
},
PluginCommand::FocusNextPane => focus_next_pane(env),
PluginCommand::FocusPreviousPane => focus_previous_pane(env),
PluginCommand::FocusLastPane => focus_last_pane(env),
PluginCommand::MoveFocus(direction) => move_focus(env, direction),
PluginCommand::MoveFocusOrTab(direction) => move_focus_or_tab(env, direction),
PluginCommand::Detach => detach(env),
Expand Down Expand Up @@ -1510,6 +1511,12 @@ fn focus_previous_pane(env: &PluginEnv) {
apply_action!(action, error_msg, env);
}

fn focus_last_pane(env: &PluginEnv) {
let action = Action::FocusLastPane;
let error_msg = || format!("Failed to focus last pane");
apply_action!(action, error_msg, env);
}

fn move_focus(env: &PluginEnv, direction: Direction) {
let error_msg = || format!("failed to move focus in plugin {}", env.name());
let action = Action::MoveFocus(direction);
Expand Down
5 changes: 5 additions & 0 deletions zellij-server/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ pub(crate) fn route_action(
.send_to_screen(ScreenInstruction::FocusPreviousPane(client_id))
.with_context(err_context)?;
},
Action::FocusLastPane => {
senders
.send_to_screen(ScreenInstruction::FocusLastPane(client_id))
.with_context(err_context)?;
},
Action::MoveFocus(direction) => {
let screen_instr = match direction {
Direction::Left => ScreenInstruction::MoveFocusLeft(client_id),
Expand Down
Loading