Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3453775
init
JunkuiZhang Aug 19, 2025
49278ce
add tests
JunkuiZhang Aug 19, 2025
f602c1d
wip
JunkuiZhang Aug 19, 2025
8e35721
checkpoint
JunkuiZhang Aug 19, 2025
422e67b
init `default-windows.json`
JunkuiZhang Aug 19, 2025
44973ef
add `"use_key_equivalents": true,`
JunkuiZhang Aug 19, 2025
d949e30
add windows special shorcuts
JunkuiZhang Aug 19, 2025
38970d8
repos `alt` key
JunkuiZhang Aug 19, 2025
60f4aa1
unshifted keys
JunkuiZhang Aug 19, 2025
ca13d02
mark all `ctrl-alt` related shortcuts
JunkuiZhang Aug 19, 2025
1d088ec
add keyboard mapper
JunkuiZhang Aug 19, 2025
cb8b7b0
checkpoint
JunkuiZhang Aug 19, 2025
fa3abe7
wip
JunkuiZhang Aug 19, 2025
366b5d1
Reload shortcuts when layout changed
JunkuiZhang Aug 20, 2025
029fa40
fix merge conflicts
JunkuiZhang Aug 20, 2025
211c08b
fix tests
JunkuiZhang Aug 20, 2025
5760fad
clippy
JunkuiZhang Aug 20, 2025
98fc52f
fix tests
JunkuiZhang Aug 20, 2025
cb09b8f
rename
JunkuiZhang Aug 25, 2025
d6b334c
update keymap
JunkuiZhang Aug 25, 2025
899e79e
fix merge conflicts
JunkuiZhang Aug 25, 2025
6dad395
try patch all `ctrl-alt-key`
JunkuiZhang Aug 25, 2025
c08a11b
repos
JunkuiZhang Aug 25, 2025
d8c9aff
init macos
JunkuiZhang Aug 25, 2025
11bc658
try fix macos
JunkuiZhang Aug 25, 2025
b27e1ec
fix
JunkuiZhang Aug 25, 2025
459f168
fix macos
JunkuiZhang Aug 25, 2025
c3c5404
fix
JunkuiZhang Aug 25, 2025
6a3b192
typo
JunkuiZhang Aug 26, 2025
144d110
fix linux
JunkuiZhang Aug 26, 2025
98ea1d8
update keymap
JunkuiZhang Aug 26, 2025
9615b71
comments
JunkuiZhang Aug 26, 2025
8cac7c6
fix `KeymapEditor`
JunkuiZhang Aug 26, 2025
a03f7e9
typo
JunkuiZhang Aug 26, 2025
8ea7671
better cfg
JunkuiZhang Aug 26, 2025
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,260 changes: 1,260 additions & 0 deletions assets/keymaps/default-windows.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions crates/docs_preprocessor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ static KEYMAP_LINUX: LazyLock<KeymapFile> = LazyLock::new(|| {
load_keymap("keymaps/default-linux.json").expect("Failed to load Linux keymap")
});

static KEYMAP_WINDOWS: LazyLock<KeymapFile> = LazyLock::new(|| {
load_keymap("keymaps/default-windows.json").expect("Failed to load Windows keymap")
});

static ALL_ACTIONS: LazyLock<Vec<ActionDef>> = LazyLock::new(dump_all_gpui_actions);

const FRONT_MATTER_COMMENT: &str = "<!-- ZED_META {} -->";
Expand Down Expand Up @@ -216,6 +220,7 @@ fn find_binding(os: &str, action: &str) -> Option<String> {
let keymap = match os {
"macos" => &KEYMAP_MACOS,
"linux" | "freebsd" => &KEYMAP_LINUX,
"windows" => &KEYMAP_WINDOWS,
_ => unreachable!("Not a valid OS: {}", os),
};

Expand Down
26 changes: 13 additions & 13 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2588,7 +2588,7 @@ impl Editor {
|| binding
.keystrokes()
.first()
.is_some_and(|keystroke| keystroke.modifiers.modified())
.is_some_and(|keystroke| keystroke.display_modifiers.modified())
}))
}

Expand Down Expand Up @@ -7686,16 +7686,16 @@ impl Editor {
.keystroke()
{
modifiers_held = modifiers_held
|| (&accept_keystroke.modifiers == modifiers
&& accept_keystroke.modifiers.modified());
|| (&accept_keystroke.display_modifiers == modifiers
&& accept_keystroke.display_modifiers.modified());
};
if let Some(accept_partial_keystroke) = self
.accept_edit_prediction_keybind(true, window, cx)
.keystroke()
{
modifiers_held = modifiers_held
|| (&accept_partial_keystroke.modifiers == modifiers
&& accept_partial_keystroke.modifiers.modified());
|| (&accept_partial_keystroke.display_modifiers == modifiers
&& accept_partial_keystroke.display_modifiers.modified());
}

if modifiers_held {
Expand Down Expand Up @@ -9044,7 +9044,7 @@ impl Editor {

let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;

let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
let modifiers_color = if accept_keystroke.display_modifiers == window.modifiers() {
Color::Accent
} else {
Color::Muted
Expand All @@ -9056,19 +9056,19 @@ impl Editor {
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.text_size(TextSize::XSmall.rems(cx))
.child(h_flex().children(ui::render_modifiers(
&accept_keystroke.modifiers,
&accept_keystroke.display_modifiers,
PlatformStyle::platform(),
Some(modifiers_color),
Some(IconSize::XSmall.rems().into()),
true,
)))
.when(is_platform_style_mac, |parent| {
parent.child(accept_keystroke.key.clone())
parent.child(accept_keystroke.display_key.clone())
})
.when(!is_platform_style_mac, |parent| {
parent.child(
Key::new(
util::capitalize(&accept_keystroke.key),
util::capitalize(&accept_keystroke.display_key),
Some(Color::Default),
)
.size(Some(IconSize::XSmall.rems().into())),
Expand Down Expand Up @@ -9171,7 +9171,7 @@ impl Editor {
max_width: Pixels,
cursor_point: Point,
style: &EditorStyle,
accept_keystroke: Option<&gpui::Keystroke>,
accept_keystroke: Option<&gpui::KeybindingKeystroke>,
_window: &Window,
cx: &mut Context<Editor>,
) -> Option<AnyElement> {
Expand Down Expand Up @@ -9249,7 +9249,7 @@ impl Editor {
accept_keystroke.as_ref(),
|el, accept_keystroke| {
el.child(h_flex().children(ui::render_modifiers(
&accept_keystroke.modifiers,
&accept_keystroke.display_modifiers,
PlatformStyle::platform(),
Some(Color::Default),
Some(IconSize::XSmall.rems().into()),
Expand Down Expand Up @@ -9319,7 +9319,7 @@ impl Editor {
.child(completion),
)
.when_some(accept_keystroke, |el, accept_keystroke| {
if !accept_keystroke.modifiers.modified() {
if !accept_keystroke.display_modifiers.modified() {
return el;
}

Expand All @@ -9338,7 +9338,7 @@ impl Editor {
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.when(is_platform_style_mac, |parent| parent.gap_1())
.child(h_flex().children(ui::render_modifiers(
&accept_keystroke.modifiers,
&accept_keystroke.display_modifiers,
PlatformStyle::platform(),
Some(if !has_completion {
Color::Muted
Expand Down
10 changes: 5 additions & 5 deletions crates/editor/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ use gpui::{
Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle,
DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId,
GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero,
Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollHandle,
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
KeybindingKeystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement,
Style, Styled, TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background,
transparent_black,
};
Expand Down Expand Up @@ -7150,7 +7150,7 @@ fn header_jump_data(
pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);

impl AcceptEditPredictionBinding {
pub fn keystroke(&self) -> Option<&Keystroke> {
pub fn keystroke(&self) -> Option<&KeybindingKeystroke> {
if let Some(binding) = self.0.as_ref() {
match &binding.keystrokes() {
[keystroke, ..] => Some(keystroke),
Expand Down
17 changes: 13 additions & 4 deletions crates/gpui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ use crate::{
AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId,
EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext,
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle,
PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource,
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
WindowHandle, WindowId, WindowInvalidator,
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, PromptBuilder,
PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle,
Reservation, ScreenCaptureSource, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem,
Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
colors::{Colors, GlobalColors},
current_platform, hash, init_app_menus,
};
Expand Down Expand Up @@ -263,6 +263,7 @@ pub struct App {
pub(crate) focus_handles: Arc<FocusMap>,
pub(crate) keymap: Rc<RefCell<Keymap>>,
pub(crate) keyboard_layout: Box<dyn PlatformKeyboardLayout>,
pub(crate) keyboard_mapper: Rc<dyn PlatformKeyboardMapper>,
pub(crate) global_action_listeners:
FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
pending_effects: VecDeque<Effect>,
Expand Down Expand Up @@ -312,6 +313,7 @@ impl App {
let text_system = Arc::new(TextSystem::new(platform.text_system()));
let entities = EntityMap::new();
let keyboard_layout = platform.keyboard_layout();
let keyboard_mapper = platform.keyboard_mapper();

let app = Rc::new_cyclic(|this| AppCell {
app: RefCell::new(App {
Expand All @@ -337,6 +339,7 @@ impl App {
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
keymap: Rc::new(RefCell::new(Keymap::default())),
keyboard_layout,
keyboard_mapper,
global_action_listeners: FxHashMap::default(),
pending_effects: VecDeque::new(),
pending_notifications: FxHashSet::default(),
Expand Down Expand Up @@ -376,6 +379,7 @@ impl App {
if let Some(app) = app.upgrade() {
let cx = &mut app.borrow_mut();
cx.keyboard_layout = cx.platform.keyboard_layout();
cx.keyboard_mapper = cx.platform.keyboard_mapper();
cx.keyboard_layout_observers
.clone()
.retain(&(), move |callback| (callback)(cx));
Expand Down Expand Up @@ -424,6 +428,11 @@ impl App {
self.keyboard_layout.as_ref()
}

/// Get the current keyboard mapper.
pub fn keyboard_mapper(&self) -> &Rc<dyn PlatformKeyboardMapper> {
&self.keyboard_mapper
}

/// Invokes a handler when the current keyboard layout changes
pub fn on_keyboard_layout_change<F>(&self, mut callback: F) -> Subscription
where
Expand Down
7 changes: 3 additions & 4 deletions crates/gpui/src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod context;
pub use binding::*;
pub use context::*;

use crate::{Action, Keystroke, is_no_action};
use crate::{Action, AsKeystroke, Keystroke, is_no_action};
use collections::{HashMap, HashSet};
use smallvec::SmallVec;
use std::any::TypeId;
Expand Down Expand Up @@ -141,7 +141,7 @@ impl Keymap {
/// only.
pub fn bindings_for_input(
&self,
input: &[Keystroke],
input: &[impl AsKeystroke],
context_stack: &[KeyContext],
) -> (SmallVec<[KeyBinding; 1]>, bool) {
let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
Expand Down Expand Up @@ -192,7 +192,6 @@ impl Keymap {

(bindings, !pending.is_empty())
}

/// Check if the given binding is enabled, given a certain key context.
/// Returns the deepest depth at which the binding matches, or None if it doesn't match.
fn binding_enabled(&self, binding: &KeyBinding, contexts: &[KeyContext]) -> Option<usize> {
Expand Down Expand Up @@ -639,7 +638,7 @@ mod tests {
fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
let actual = keymap
.bindings_for_action(action)
.map(|binding| binding.keystrokes[0].unparse())
.map(|binding| binding.keystrokes[0].inner.unparse())
.collect::<Vec<_>>();
assert_eq!(actual, expected, "{:?}", action);
}
Expand Down
49 changes: 28 additions & 21 deletions crates/gpui/src/keymap/binding.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use std::rc::Rc;

use collections::HashMap;

use crate::{Action, InvalidKeystrokeError, KeyBindingContextPredicate, Keystroke, SharedString};
use crate::{
Action, AsKeystroke, DummyKeyboardMapper, InvalidKeystrokeError, KeyBindingContextPredicate,
KeybindingKeystroke, Keystroke, PlatformKeyboardMapper, SharedString,
};
use smallvec::SmallVec;

/// A keybinding and its associated metadata, from the keymap.
pub struct KeyBinding {
pub(crate) action: Box<dyn Action>,
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
pub(crate) keystrokes: SmallVec<[KeybindingKeystroke; 2]>,
pub(crate) context_predicate: Option<Rc<KeyBindingContextPredicate>>,
pub(crate) meta: Option<KeyBindingMetaIndex>,
/// The json input string used when building the keybinding, if any
Expand All @@ -32,32 +33,38 @@ impl KeyBinding {
pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
let context_predicate =
context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into());
Self::load(keystrokes, Box::new(action), context_predicate, None, None).unwrap()
Self::load(
keystrokes,
Box::new(action),
context_predicate,
false,
None,
&DummyKeyboardMapper,
)
.unwrap()
}

/// Load a keybinding from the given raw data.
pub fn load(
keystrokes: &str,
action: Box<dyn Action>,
context_predicate: Option<Rc<KeyBindingContextPredicate>>,
key_equivalents: Option<&HashMap<char, char>>,
use_key_equivalents: bool,
action_input: Option<SharedString>,
keyboard_mapper: &dyn PlatformKeyboardMapper,
) -> std::result::Result<Self, InvalidKeystrokeError> {
let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes
let keystrokes: SmallVec<[KeybindingKeystroke; 2]> = keystrokes
.split_whitespace()
.map(Keystroke::parse)
.map(|source| {
let keystroke = Keystroke::parse(source)?;
Ok(KeybindingKeystroke::new(
keystroke,
use_key_equivalents,
keyboard_mapper,
))
})
.collect::<std::result::Result<_, _>>()?;

if let Some(equivalents) = key_equivalents {
for keystroke in keystrokes.iter_mut() {
if keystroke.key.chars().count() == 1
&& let Some(key) = equivalents.get(&keystroke.key.chars().next().unwrap())
{
keystroke.key = key.to_string();
}
}
}

Ok(Self {
keystrokes,
action,
Expand All @@ -79,13 +86,13 @@ impl KeyBinding {
}

/// Check if the given keystrokes match this binding.
pub fn match_keystrokes(&self, typed: &[Keystroke]) -> Option<bool> {
pub fn match_keystrokes(&self, typed: &[impl AsKeystroke]) -> Option<bool> {
if self.keystrokes.len() < typed.len() {
return None;
}

for (target, typed) in self.keystrokes.iter().zip(typed.iter()) {
if !typed.should_match(target) {
if !typed.as_keystroke().should_match(target) {
return None;
}
}
Expand All @@ -94,7 +101,7 @@ impl KeyBinding {
}

/// Get the keystrokes associated with this binding
pub fn keystrokes(&self) -> &[Keystroke] {
pub fn keystrokes(&self) -> &[KeybindingKeystroke] {
self.keystrokes.as_slice()
}

Expand Down
6 changes: 4 additions & 2 deletions crates/gpui/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ pub(crate) trait Platform: 'static {

fn on_quit(&self, callback: Box<dyn FnMut()>);
fn on_reopen(&self, callback: Box<dyn FnMut()>);
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);

fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
Expand All @@ -251,7 +250,6 @@ pub(crate) trait Platform: 'static {
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;

fn compositor_name(&self) -> &'static str {
""
Expand All @@ -272,6 +270,10 @@ pub(crate) trait Platform: 'static {
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
fn delete_credentials(&self, url: &str) -> Task<Result<()>>;

fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
}

/// A handle to a platform's display, e.g. a monitor or laptop screen.
Expand Down
Loading
Loading