Add space switching shortcuts

This commit is contained in:
2026-05-08 08:57:18 -04:00
parent 43ab4be548
commit cdb82bcace
7 changed files with 142 additions and 2 deletions
+5
View File
@@ -23,7 +23,9 @@ actions!(
OpenTaskManager, OpenTaskManager,
Quit, Quit,
RestoreClosedTab, RestoreClosedTab,
SelectNextSpace,
SelectNextTab, SelectNextTab,
SelectPreviousSpace,
SelectPreviousTab, SelectPreviousTab,
SplitRight, SplitRight,
ToggleFavoriteTab, ToggleFavoriteTab,
@@ -59,6 +61,9 @@ fn main() {
MenuItem::separator(), MenuItem::separator(),
MenuItem::action("Restore Closed Tab", RestoreClosedTab), MenuItem::action("Restore Closed Tab", RestoreClosedTab),
MenuItem::separator(), MenuItem::separator(),
MenuItem::action("Next Space", SelectNextSpace),
MenuItem::action("Previous Space", SelectPreviousSpace),
MenuItem::separator(),
MenuItem::action("Open Downloads", OpenDownloads), MenuItem::action("Open Downloads", OpenDownloads),
MenuItem::action("Open History", OpenHistory), MenuItem::action("Open History", OpenHistory),
MenuItem::action("Open Task Manager", OpenTaskManager), MenuItem::action("Open Task Manager", OpenTaskManager),
+1
View File
@@ -8,6 +8,7 @@ mod reading_list;
mod render; mod render;
mod sidebar; mod sidebar;
mod site_permissions; mod site_permissions;
mod spaces;
mod splits; mod splits;
mod tab_groups; mod tab_groups;
+2
View File
@@ -51,7 +51,9 @@ impl ElyShell {
.on_action(cx.listener(Self::on_open_settings)) .on_action(cx.listener(Self::on_open_settings))
.on_action(cx.listener(Self::on_open_task_manager)) .on_action(cx.listener(Self::on_open_task_manager))
.on_action(cx.listener(Self::on_restore_closed_tab)) .on_action(cx.listener(Self::on_restore_closed_tab))
.on_action(cx.listener(Self::on_select_next_space))
.on_action(cx.listener(Self::on_select_next_tab)) .on_action(cx.listener(Self::on_select_next_tab))
.on_action(cx.listener(Self::on_select_previous_space))
.on_action(cx.listener(Self::on_select_previous_tab)) .on_action(cx.listener(Self::on_select_previous_tab))
.on_action(cx.listener(Self::on_split_right)) .on_action(cx.listener(Self::on_split_right))
.on_action(cx.listener(Self::on_toggle_favorite_tab)) .on_action(cx.listener(Self::on_toggle_favorite_tab))
+34
View File
@@ -0,0 +1,34 @@
use gpui::{Context, Window};
use super::{ElyShell, ShellState};
use crate::{SelectNextSpace, SelectPreviousSpace};
impl ElyShell {
pub(super) fn on_select_next_space(
&mut self,
_: &SelectNextSpace,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let ShellState::Ready(core) = &mut self.state
&& core.select_next_space().is_ok()
{
self.sync_address_input(window, cx);
cx.notify();
}
}
pub(super) fn on_select_previous_space(
&mut self,
_: &SelectPreviousSpace,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let ShellState::Ready(core) = &mut self.state
&& core.select_previous_space().is_ok()
{
self.sync_address_input(window, cx);
cx.notify();
}
}
}
+51 -2
View File
@@ -4,8 +4,8 @@ use gpui::{App, KeyBinding};
use crate::{ use crate::{
CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab, CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab,
OpenSettings, OpenTaskManager, Quit, RestoreClosedTab, SelectNextTab, SelectPreviousTab, OpenSettings, OpenTaskManager, Quit, RestoreClosedTab, SelectNextSpace, SelectNextTab,
SplitRight, ToggleFavoriteTab, ToggleSidebar, SelectPreviousSpace, SelectPreviousTab, SplitRight, ToggleFavoriteTab, ToggleSidebar,
}; };
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -30,6 +30,8 @@ pub(crate) enum ShortcutAction {
OpenNewTab, OpenNewTab,
CloseCurrentTab, CloseCurrentTab,
RestoreClosedTab, RestoreClosedTab,
SelectNextSpace,
SelectPreviousSpace,
SelectNextTab, SelectNextTab,
SelectPreviousTab, SelectPreviousTab,
SplitRight, SplitRight,
@@ -50,6 +52,8 @@ impl ShortcutAction {
Self::OpenNewTab => "New Tab", Self::OpenNewTab => "New Tab",
Self::CloseCurrentTab => "Close Tab", Self::CloseCurrentTab => "Close Tab",
Self::RestoreClosedTab => "Restore Closed Tab", Self::RestoreClosedTab => "Restore Closed Tab",
Self::SelectNextSpace => "Next Space",
Self::SelectPreviousSpace => "Previous Space",
Self::SelectNextTab => "Next Tab", Self::SelectNextTab => "Next Tab",
Self::SelectPreviousTab => "Previous Tab", Self::SelectPreviousTab => "Previous Tab",
Self::SplitRight => "Split Right", Self::SplitRight => "Split Right",
@@ -69,6 +73,8 @@ impl ShortcutAction {
Self::OpenNewTab Self::OpenNewTab
| Self::CloseCurrentTab | Self::CloseCurrentTab
| Self::RestoreClosedTab | Self::RestoreClosedTab
| Self::SelectNextSpace
| Self::SelectPreviousSpace
| Self::SelectNextTab | Self::SelectNextTab
| Self::SelectPreviousTab | Self::SelectPreviousTab
| Self::SplitRight | Self::SplitRight
@@ -87,6 +93,8 @@ impl ShortcutAction {
Self::OpenNewTab => Some(">new-tab"), Self::OpenNewTab => Some(">new-tab"),
Self::CloseCurrentTab => Some(">close-tab"), Self::CloseCurrentTab => Some(">close-tab"),
Self::RestoreClosedTab => Some(">restore-tab"), Self::RestoreClosedTab => Some(">restore-tab"),
Self::SelectNextSpace => None,
Self::SelectPreviousSpace => None,
Self::SelectNextTab => None, Self::SelectNextTab => None,
Self::SelectPreviousTab => None, Self::SelectPreviousTab => None,
Self::SplitRight => Some(">split-right"), Self::SplitRight => Some(">split-right"),
@@ -127,6 +135,8 @@ pub(crate) const SHORTCUT_ACTIONS: &[ShortcutAction] = &[
ShortcutAction::OpenNewTab, ShortcutAction::OpenNewTab,
ShortcutAction::CloseCurrentTab, ShortcutAction::CloseCurrentTab,
ShortcutAction::RestoreClosedTab, ShortcutAction::RestoreClosedTab,
ShortcutAction::SelectNextSpace,
ShortcutAction::SelectPreviousSpace,
ShortcutAction::SelectNextTab, ShortcutAction::SelectNextTab,
ShortcutAction::SelectPreviousTab, ShortcutAction::SelectPreviousTab,
ShortcutAction::SplitRight, ShortcutAction::SplitRight,
@@ -164,6 +174,10 @@ pub(crate) const SHORTCUT_BINDINGS: &[ShortcutBinding] = &[
shortcut(ShortcutAction::ToggleFavoriteTab, ShortcutPlatform::WindowsLinux, "ctrl-shift-f"), shortcut(ShortcutAction::ToggleFavoriteTab, ShortcutPlatform::WindowsLinux, "ctrl-shift-f"),
shortcut(ShortcutAction::FocusCommandMode, ShortcutPlatform::Macos, "cmd-shift-p"), shortcut(ShortcutAction::FocusCommandMode, ShortcutPlatform::Macos, "cmd-shift-p"),
shortcut(ShortcutAction::FocusCommandMode, ShortcutPlatform::WindowsLinux, "ctrl-shift-p"), shortcut(ShortcutAction::FocusCommandMode, ShortcutPlatform::WindowsLinux, "ctrl-shift-p"),
shortcut(ShortcutAction::SelectNextSpace, ShortcutPlatform::Macos, "cmd-alt-right"),
shortcut(ShortcutAction::SelectNextSpace, ShortcutPlatform::WindowsLinux, "ctrl-alt-right"),
shortcut(ShortcutAction::SelectPreviousSpace, ShortcutPlatform::Macos, "cmd-alt-left"),
shortcut(ShortcutAction::SelectPreviousSpace, ShortcutPlatform::WindowsLinux, "ctrl-alt-left"),
shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::Macos, "cmd-shift-]"), shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::Macos, "cmd-shift-]"),
shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::Macos, "cmd-alt-down"), shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::Macos, "cmd-alt-down"),
shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::WindowsLinux, "ctrl-tab"), shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::WindowsLinux, "ctrl-tab"),
@@ -239,7 +253,13 @@ impl ShortcutBinding {
ShortcutAction::RestoreClosedTab => { ShortcutAction::RestoreClosedTab => {
KeyBinding::new(self.keystroke, RestoreClosedTab, None) KeyBinding::new(self.keystroke, RestoreClosedTab, None)
} }
ShortcutAction::SelectNextSpace => {
KeyBinding::new(self.keystroke, SelectNextSpace, None)
}
ShortcutAction::SelectNextTab => KeyBinding::new(self.keystroke, SelectNextTab, None), ShortcutAction::SelectNextTab => KeyBinding::new(self.keystroke, SelectNextTab, None),
ShortcutAction::SelectPreviousSpace => {
KeyBinding::new(self.keystroke, SelectPreviousSpace, None)
}
ShortcutAction::SelectPreviousTab => { ShortcutAction::SelectPreviousTab => {
KeyBinding::new(self.keystroke, SelectPreviousTab, None) KeyBinding::new(self.keystroke, SelectPreviousTab, None)
} }
@@ -310,6 +330,35 @@ mod tests {
assert_eq!(bindings, vec!["Cmd + B".to_string(), "Ctrl + B".to_string()]); assert_eq!(bindings, vec!["Cmd + B".to_string(), "Ctrl + B".to_string()]);
} }
#[test]
fn space_switch_shortcuts_have_platform_bindings() {
let next_bindings =
bindings_for_action(ShortcutAction::SelectNextSpace, ShortcutPlatform::Macos)
.chain(bindings_for_action(
ShortcutAction::SelectNextSpace,
ShortcutPlatform::WindowsLinux,
))
.map(|binding| binding.display_keystroke())
.collect::<Vec<_>>();
let previous_bindings =
bindings_for_action(ShortcutAction::SelectPreviousSpace, ShortcutPlatform::Macos)
.chain(bindings_for_action(
ShortcutAction::SelectPreviousSpace,
ShortcutPlatform::WindowsLinux,
))
.map(|binding| binding.display_keystroke())
.collect::<Vec<_>>();
assert_eq!(
next_bindings,
vec!["Cmd + Option + RIGHT".to_string(), "Ctrl + Alt + RIGHT".to_string()]
);
assert_eq!(
previous_bindings,
vec!["Cmd + Option + LEFT".to_string(), "Ctrl + Alt + LEFT".to_string()]
);
}
#[test] #[test]
fn every_declared_action_has_a_binding() { fn every_declared_action_has_a_binding() {
for action in SHORTCUT_ACTIONS { for action in SHORTCUT_ACTIONS {
+26
View File
@@ -229,6 +229,32 @@ impl BrowserCore {
Ok(tab_id) Ok(tab_id)
} }
pub fn select_next_space(&mut self) -> Result<TabId, CoreError> {
let spaces = self.sorted_spaces();
let Some(active_index) =
spaces.iter().position(|space| space.id() == &self.active_space_id)
else {
return Err(CoreError::SpaceNotFound { id: self.active_space_id.clone() });
};
let next_index = (active_index + 1) % spaces.len();
let space_id = spaces[next_index].id().clone();
self.select_space(&space_id)
}
pub fn select_previous_space(&mut self) -> Result<TabId, CoreError> {
let spaces = self.sorted_spaces();
let Some(active_index) =
spaces.iter().position(|space| space.id() == &self.active_space_id)
else {
return Err(CoreError::SpaceNotFound { id: self.active_space_id.clone() });
};
let previous_index = if active_index == 0 { spaces.len() - 1 } else { active_index - 1 };
let space_id = spaces[previous_index].id().clone();
self.select_space(&space_id)
}
pub fn set_active_space_archive_policy( pub fn set_active_space_archive_policy(
&mut self, &mut self,
archive_policy: ArchivePolicy, archive_policy: ArchivePolicy,
+23
View File
@@ -152,6 +152,29 @@ fn space_sidebar_width_updates_selected_space() -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
#[test]
fn selecting_adjacent_spaces_uses_sort_order_with_wraparound() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let work_space_id = core.snapshot()?.active_space_id;
let research_space_id = core.create_space("Research", "R", 0x9fc9a2)?;
let personal_space_id = core.create_space("Personal", "P", 0x8eb7d4)?;
core.set_space_sort_key(&work_space_id, 20)?;
core.set_space_sort_key(&research_space_id, 10)?;
core.set_space_sort_key(&personal_space_id, 30)?;
core.select_space(&work_space_id)?;
core.select_next_space()?;
assert_eq!(core.snapshot()?.active_space_id, personal_space_id);
core.select_next_space()?;
assert_eq!(core.snapshot()?.active_space_id, research_space_id);
core.select_previous_space()?;
assert_eq!(core.snapshot()?.active_space_id, personal_space_id);
Ok(())
}
fn active_space_updated_at( fn active_space_updated_at(
core: &BrowserCore, core: &BrowserCore,
space_id: &ely_domain::SpaceId, space_id: &ely_domain::SpaceId,