Files
ELY-Browser/crates/ely_app/src/shortcuts.rs
T
ZacharyZhang-NY 22186bb8a4 Match Continue card column ratio + add Cmd+K omnibar focus
- Continue card: switch from grid_cols(2) (50/50) to flex with a
  flex-1 history column and a fixed 240 px reading-list cover, matching
  the design's grid-template-columns: 1fr 240px.
- Cmd+K (Ctrl+K on Windows/Linux) now aliases FocusAddressBar so the
  ⌘K affordance shown in Arc/Dia/Linear-style designs lands on the
  omnibar without disturbing the existing Cmd+L mapping.
2026-05-09 19:35:46 -04:00

367 lines
15 KiB
Rust

use gpui::{App, KeyBinding};
use serde::{Deserialize, Serialize};
mod profile;
pub(crate) use profile::{
ELYKEYS_FILE_EXTENSION, ShortcutProfile, parse_shortcut_profile_json,
shortcut_rebinding_key_bindings,
};
use crate::{
CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab,
OpenPrivateWindow, OpenSettings, OpenTaskManager, Quit, ResetZoom, RestoreClosedTab,
SelectNextSpace, SelectNextTab, SelectPreviousSpace, SelectPreviousTab, SplitRight,
ToggleFavoriteTab, ToggleSidebar, ZoomIn, ZoomOut,
};
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ShortcutPlatform {
Macos,
WindowsLinux,
}
impl ShortcutPlatform {
pub(crate) fn label(self) -> &'static str {
match self {
Self::Macos => "macOS",
Self::WindowsLinux => "Windows/Linux",
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ShortcutAction {
FocusAddressBar,
FocusCommandMode,
OpenNewTab,
OpenPrivateWindow,
CloseCurrentTab,
RestoreClosedTab,
SelectNextSpace,
SelectPreviousSpace,
SelectNextTab,
SelectPreviousTab,
SplitRight,
ToggleSidebar,
ToggleFavoriteTab,
ZoomIn,
ZoomOut,
ResetZoom,
OpenDownloads,
OpenHistory,
OpenSettings,
OpenTaskManager,
Quit,
}
impl ShortcutAction {
pub(crate) fn label(self) -> &'static str {
match self {
Self::FocusAddressBar => "Command Bar",
Self::FocusCommandMode => "Command Mode",
Self::OpenNewTab => "New Tab",
Self::OpenPrivateWindow => "New Private Window",
Self::CloseCurrentTab => "Close Tab",
Self::RestoreClosedTab => "Restore Closed Tab",
Self::SelectNextSpace => "Next Space",
Self::SelectPreviousSpace => "Previous Space",
Self::SelectNextTab => "Next Tab",
Self::SelectPreviousTab => "Previous Tab",
Self::SplitRight => "Split Right",
Self::ToggleSidebar => "Toggle Sidebar",
Self::ToggleFavoriteTab => "Toggle Favorite",
Self::ZoomIn => "Zoom In",
Self::ZoomOut => "Zoom Out",
Self::ResetZoom => "Reset Zoom",
Self::OpenDownloads => "Open Downloads",
Self::OpenHistory => "Open History",
Self::OpenSettings => "Open Settings",
Self::OpenTaskManager => "Task Manager",
Self::Quit => "Quit ELY Browser",
}
}
pub(crate) fn category(self) -> &'static str {
match self {
Self::FocusAddressBar | Self::FocusCommandMode => "Command",
Self::OpenNewTab
| Self::OpenPrivateWindow
| Self::CloseCurrentTab
| Self::RestoreClosedTab
| Self::SelectNextSpace
| Self::SelectPreviousSpace
| Self::SelectNextTab
| Self::SelectPreviousTab
| Self::SplitRight
| Self::ToggleSidebar
| Self::ToggleFavoriteTab
| Self::ZoomIn
| Self::ZoomOut
| Self::ResetZoom => "Tabs",
Self::OpenDownloads | Self::OpenHistory => "Library",
Self::OpenSettings | Self::OpenTaskManager => "System",
Self::Quit => "Application",
}
}
pub(crate) fn command(self) -> Option<&'static str> {
match self {
Self::FocusAddressBar => None,
Self::FocusCommandMode => None,
Self::OpenNewTab => Some(">new-tab"),
Self::OpenPrivateWindow => None,
Self::CloseCurrentTab => Some(">close-tab"),
Self::RestoreClosedTab => Some(">restore-tab"),
Self::SelectNextSpace => None,
Self::SelectPreviousSpace => None,
Self::SelectNextTab => None,
Self::SelectPreviousTab => None,
Self::SplitRight => Some(">split-right"),
Self::ToggleSidebar => None,
Self::ToggleFavoriteTab => Some(">favorite"),
Self::ZoomIn => Some(">zoom-in"),
Self::ZoomOut => Some(">zoom-out"),
Self::ResetZoom => Some(">reset-zoom"),
Self::OpenDownloads => Some(">open-downloads"),
Self::OpenHistory => Some(">open-history"),
Self::OpenSettings => Some(">open-settings"),
Self::OpenTaskManager => Some(">open-task-manager"),
Self::Quit => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ShortcutBinding {
action: ShortcutAction,
platform: ShortcutPlatform,
keystroke: &'static str,
}
impl ShortcutBinding {
pub(crate) fn action(self) -> ShortcutAction {
self.action
}
pub(crate) fn platform(self) -> ShortcutPlatform {
self.platform
}
pub(crate) fn keystroke(self) -> &'static str {
self.keystroke
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ShortcutConflict {
pub(crate) platform: ShortcutPlatform,
pub(crate) keystroke: String,
pub(crate) actions: Vec<ShortcutAction>,
}
pub(crate) const SHORTCUT_ACTIONS: &[ShortcutAction] = &[
ShortcutAction::FocusAddressBar,
ShortcutAction::FocusCommandMode,
ShortcutAction::OpenNewTab,
ShortcutAction::OpenPrivateWindow,
ShortcutAction::CloseCurrentTab,
ShortcutAction::RestoreClosedTab,
ShortcutAction::SelectNextSpace,
ShortcutAction::SelectPreviousSpace,
ShortcutAction::SelectNextTab,
ShortcutAction::SelectPreviousTab,
ShortcutAction::SplitRight,
ShortcutAction::ToggleSidebar,
ShortcutAction::ToggleFavoriteTab,
ShortcutAction::ZoomIn,
ShortcutAction::ZoomOut,
ShortcutAction::ResetZoom,
ShortcutAction::OpenDownloads,
ShortcutAction::OpenHistory,
ShortcutAction::OpenSettings,
ShortcutAction::OpenTaskManager,
ShortcutAction::Quit,
];
pub(crate) const SHORTCUT_BINDINGS: &[ShortcutBinding] = &[
shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::Macos, "cmd-t"),
shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::WindowsLinux, "ctrl-t"),
shortcut(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::Macos, "cmd-shift-n"),
shortcut(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::WindowsLinux, "ctrl-shift-n"),
shortcut(ShortcutAction::SplitRight, ShortcutPlatform::Macos, "cmd-\\"),
shortcut(ShortcutAction::SplitRight, ShortcutPlatform::WindowsLinux, "ctrl-\\"),
shortcut(ShortcutAction::ToggleSidebar, ShortcutPlatform::Macos, "cmd-b"),
shortcut(ShortcutAction::ToggleSidebar, ShortcutPlatform::WindowsLinux, "ctrl-b"),
shortcut(ShortcutAction::OpenDownloads, ShortcutPlatform::Macos, "cmd-shift-j"),
shortcut(ShortcutAction::OpenDownloads, ShortcutPlatform::WindowsLinux, "ctrl-shift-j"),
shortcut(ShortcutAction::OpenHistory, ShortcutPlatform::Macos, "cmd-y"),
shortcut(ShortcutAction::OpenHistory, ShortcutPlatform::WindowsLinux, "ctrl-h"),
shortcut(ShortcutAction::OpenTaskManager, ShortcutPlatform::Macos, "cmd-escape"),
shortcut(ShortcutAction::OpenTaskManager, ShortcutPlatform::WindowsLinux, "shift-escape"),
shortcut(ShortcutAction::OpenSettings, ShortcutPlatform::Macos, "cmd-,"),
shortcut(ShortcutAction::OpenSettings, ShortcutPlatform::WindowsLinux, "ctrl-,"),
shortcut(ShortcutAction::FocusAddressBar, ShortcutPlatform::Macos, "cmd-l"),
shortcut(ShortcutAction::FocusAddressBar, ShortcutPlatform::Macos, "cmd-k"),
shortcut(ShortcutAction::FocusAddressBar, ShortcutPlatform::WindowsLinux, "ctrl-l"),
shortcut(ShortcutAction::FocusAddressBar, ShortcutPlatform::WindowsLinux, "ctrl-k"),
shortcut(ShortcutAction::CloseCurrentTab, ShortcutPlatform::Macos, "cmd-w"),
shortcut(ShortcutAction::CloseCurrentTab, ShortcutPlatform::WindowsLinux, "ctrl-w"),
shortcut(ShortcutAction::RestoreClosedTab, ShortcutPlatform::Macos, "cmd-shift-t"),
shortcut(ShortcutAction::RestoreClosedTab, ShortcutPlatform::WindowsLinux, "ctrl-shift-t"),
shortcut(ShortcutAction::ToggleFavoriteTab, ShortcutPlatform::Macos, "cmd-shift-f"),
shortcut(ShortcutAction::ToggleFavoriteTab, ShortcutPlatform::WindowsLinux, "ctrl-shift-f"),
shortcut(ShortcutAction::ZoomIn, ShortcutPlatform::Macos, "cmd-="),
shortcut(ShortcutAction::ZoomIn, ShortcutPlatform::WindowsLinux, "ctrl-="),
shortcut(ShortcutAction::ZoomOut, ShortcutPlatform::Macos, "cmd--"),
shortcut(ShortcutAction::ZoomOut, ShortcutPlatform::WindowsLinux, "ctrl--"),
shortcut(ShortcutAction::ResetZoom, ShortcutPlatform::Macos, "cmd-0"),
shortcut(ShortcutAction::ResetZoom, ShortcutPlatform::WindowsLinux, "ctrl-0"),
shortcut(ShortcutAction::FocusCommandMode, ShortcutPlatform::Macos, "cmd-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-alt-down"),
shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::WindowsLinux, "ctrl-tab"),
shortcut(ShortcutAction::SelectNextTab, ShortcutPlatform::WindowsLinux, "ctrl-alt-down"),
shortcut(ShortcutAction::SelectPreviousTab, ShortcutPlatform::Macos, "cmd-shift-["),
shortcut(ShortcutAction::SelectPreviousTab, ShortcutPlatform::Macos, "cmd-alt-up"),
shortcut(ShortcutAction::SelectPreviousTab, ShortcutPlatform::WindowsLinux, "ctrl-shift-tab"),
shortcut(ShortcutAction::SelectPreviousTab, ShortcutPlatform::WindowsLinux, "ctrl-alt-up"),
shortcut(ShortcutAction::Quit, ShortcutPlatform::Macos, "cmd-q"),
];
const fn shortcut(
action: ShortcutAction,
platform: ShortcutPlatform,
keystroke: &'static str,
) -> ShortcutBinding {
ShortcutBinding { action, platform, keystroke }
}
pub(crate) fn bind_shortcuts(cx: &mut App) {
cx.bind_keys(SHORTCUT_BINDINGS.iter().map(|binding| binding.key_binding()));
}
impl ShortcutBinding {
fn key_binding(self) -> KeyBinding {
key_binding_for_action(self.action, self.keystroke)
}
}
pub(crate) fn key_binding_for_action(action: ShortcutAction, keystroke: &str) -> KeyBinding {
match action {
ShortcutAction::CloseCurrentTab => KeyBinding::new(keystroke, CloseCurrentTab, None),
ShortcutAction::FocusAddressBar => KeyBinding::new(keystroke, FocusAddressBar, None),
ShortcutAction::FocusCommandMode => KeyBinding::new(keystroke, FocusCommandMode, None),
ShortcutAction::OpenDownloads => KeyBinding::new(keystroke, OpenDownloads, None),
ShortcutAction::OpenHistory => KeyBinding::new(keystroke, OpenHistory, None),
ShortcutAction::OpenNewTab => KeyBinding::new(keystroke, OpenNewTab, None),
ShortcutAction::OpenPrivateWindow => KeyBinding::new(keystroke, OpenPrivateWindow, None),
ShortcutAction::OpenSettings => KeyBinding::new(keystroke, OpenSettings, None),
ShortcutAction::OpenTaskManager => KeyBinding::new(keystroke, OpenTaskManager, None),
ShortcutAction::Quit => KeyBinding::new(keystroke, Quit, None),
ShortcutAction::ResetZoom => KeyBinding::new(keystroke, ResetZoom, None),
ShortcutAction::RestoreClosedTab => KeyBinding::new(keystroke, RestoreClosedTab, None),
ShortcutAction::SelectNextSpace => KeyBinding::new(keystroke, SelectNextSpace, None),
ShortcutAction::SelectNextTab => KeyBinding::new(keystroke, SelectNextTab, None),
ShortcutAction::SelectPreviousSpace => {
KeyBinding::new(keystroke, SelectPreviousSpace, None)
}
ShortcutAction::SelectPreviousTab => KeyBinding::new(keystroke, SelectPreviousTab, None),
ShortcutAction::SplitRight => KeyBinding::new(keystroke, SplitRight, None),
ShortcutAction::ToggleFavoriteTab => KeyBinding::new(keystroke, ToggleFavoriteTab, None),
ShortcutAction::ToggleSidebar => KeyBinding::new(keystroke, ToggleSidebar, None),
ShortcutAction::ZoomIn => KeyBinding::new(keystroke, ZoomIn, None),
ShortcutAction::ZoomOut => KeyBinding::new(keystroke, ZoomOut, None),
}
}
pub(crate) fn display_keystroke(keystroke: &str, platform: ShortcutPlatform) -> String {
keystroke
.split('-')
.map(|part| display_key_part(part, platform))
.collect::<Vec<_>>()
.join(" + ")
}
fn display_key_part(part: &str, platform: ShortcutPlatform) -> String {
match part {
"cmd" => "Cmd".to_string(),
"ctrl" => "Ctrl".to_string(),
"alt" if platform == ShortcutPlatform::Macos => "Option".to_string(),
"alt" => "Alt".to_string(),
"shift" => "Shift".to_string(),
"escape" => "Esc".to_string(),
value => value.to_ascii_uppercase(),
}
}
#[cfg(test)]
mod tests {
use super::{
SHORTCUT_ACTIONS, SHORTCUT_BINDINGS, ShortcutAction, ShortcutPlatform, ShortcutProfile,
};
#[test]
fn registered_shortcuts_have_no_conflicts() {
assert_eq!(ShortcutProfile::default_profile().conflicts(), Vec::new());
}
#[test]
fn open_settings_shortcut_has_platform_bindings() {
let bindings = platform_labels(ShortcutAction::OpenSettings);
assert_eq!(bindings, vec!["Cmd + ,".to_string(), "Ctrl + ,".to_string()]);
}
#[test]
fn private_window_shortcut_has_platform_bindings() {
let bindings = platform_labels(ShortcutAction::OpenPrivateWindow);
assert_eq!(bindings, vec!["Cmd + Shift + N".to_string(), "Ctrl + Shift + N".to_string()]);
}
#[test]
fn toggle_sidebar_shortcut_has_platform_bindings() {
let bindings = platform_labels(ShortcutAction::ToggleSidebar);
assert_eq!(bindings, vec!["Cmd + B".to_string(), "Ctrl + B".to_string()]);
}
#[test]
fn space_switch_shortcuts_have_platform_bindings() {
let next_bindings = platform_labels(ShortcutAction::SelectNextSpace);
let previous_bindings = platform_labels(ShortcutAction::SelectPreviousSpace);
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]
fn every_declared_action_has_a_binding() {
for action in SHORTCUT_ACTIONS {
assert!(SHORTCUT_BINDINGS.iter().any(|binding| binding.action() == *action));
}
}
fn platform_labels(action: ShortcutAction) -> Vec<String> {
let profile = ShortcutProfile::default_profile();
[ShortcutPlatform::Macos, ShortcutPlatform::WindowsLinux]
.into_iter()
.map(|platform| profile.display_bindings_for_action(action, platform))
.collect()
}
}