Detailed changes
@@ -11,7 +11,7 @@ use std::{
sync::Arc,
};
use theme::ActiveTheme;
-use ui::{v_stack, HighlightedLabel, StyledExt};
+use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
@@ -318,66 +318,16 @@ impl PickerDelegate for CommandPaletteDelegate {
.rounded_md()
.when(selected, |this| this.bg(colors.ghost_element_selected))
.hover(|this| this.bg(colors.ghost_element_hover))
- .child(HighlightedLabel::new(
- command.name.clone(),
- r#match.positions.clone(),
- ))
+ .child(
+ h_stack()
+ .justify_between()
+ .child(HighlightedLabel::new(
+ command.name.clone(),
+ r#match.positions.clone(),
+ ))
+ .children(KeyBinding::for_action(&*command.action, cx)),
+ )
}
-
- // fn render_match(
- // &self,
- // ix: usize,
- // mouse_state: &mut MouseState,
- // selected: bool,
- // cx: &gpui::AppContext,
- // ) -> AnyElement<Picker<Self>> {
- // let mat = &self.matches[ix];
- // let command = &self.actions[mat.candidate_id];
- // let theme = theme::current(cx);
- // let style = theme.picker.item.in_state(selected).style_for(mouse_state);
- // let key_style = &theme.command_palette.key.in_state(selected);
- // let keystroke_spacing = theme.command_palette.keystroke_spacing;
-
- // Flex::row()
- // .with_child(
- // Label::new(mat.string.clone(), style.label.clone())
- // .with_highlights(mat.positions.clone()),
- // )
- // .with_children(command.keystrokes.iter().map(|keystroke| {
- // Flex::row()
- // .with_children(
- // [
- // (keystroke.ctrl, "^"),
- // (keystroke.alt, "⌥"),
- // (keystroke.cmd, "⌘"),
- // (keystroke.shift, "⇧"),
- // ]
- // .into_iter()
- // .filter_map(|(modifier, label)| {
- // if modifier {
- // Some(
- // Label::new(label, key_style.label.clone())
- // .contained()
- // .with_style(key_style.container),
- // )
- // } else {
- // None
- // }
- // }),
- // )
- // .with_child(
- // Label::new(keystroke.key.clone(), key_style.label.clone())
- // .contained()
- // .with_style(key_style.container),
- // )
- // .contained()
- // .with_margin_left(keystroke_spacing)
- // .flex_float()
- // }))
- // .contained()
- // .with_style(style.container)
- // .into_any()
- // }
}
fn humanize_action_name(name: &str) -> String {
@@ -1,7 +1,7 @@
use crate::{
build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle,
- FocusId, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent, Pixels,
- Style, StyleRefinement, ViewContext, WindowContext,
+ FocusId, KeyBinding, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent,
+ Pixels, Style, StyleRefinement, ViewContext, WindowContext,
};
use collections::HashMap;
use parking_lot::Mutex;
@@ -145,6 +145,15 @@ impl DispatchTree {
actions
}
+ pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
+ self.keymap
+ .lock()
+ .bindings_for_action(action.type_id())
+ .filter(|candidate| candidate.action.partial_eq(action))
+ .cloned()
+ .collect()
+ }
+
pub fn dispatch_key(
&mut self,
keystroke: &Keystroke,
@@ -3,9 +3,19 @@ use anyhow::Result;
use smallvec::SmallVec;
pub struct KeyBinding {
- action: Box<dyn Action>,
- pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
- pub(super) context_predicate: Option<KeyBindingContextPredicate>,
+ pub(crate) action: Box<dyn Action>,
+ pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
+ pub(crate) context_predicate: Option<KeyBindingContextPredicate>,
+}
+
+impl Clone for KeyBinding {
+ fn clone(&self) -> Self {
+ KeyBinding {
+ action: self.action.boxed_clone(),
+ keystrokes: self.keystrokes.clone(),
+ context_predicate: self.context_predicate.clone(),
+ }
+ }
}
impl KeyBinding {
@@ -3,13 +3,13 @@ use crate::{
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
- InputEvent, IsZero, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext, Modifiers,
- MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
- PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
- PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
- SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
- TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
- WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+ InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext,
+ Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path,
+ Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+ PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
+ RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
+ Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
+ WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
@@ -1377,6 +1377,13 @@ impl<'a> WindowContext<'a> {
Vec::new()
}
}
+
+ pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
+ self.window
+ .current_frame
+ .dispatch_tree
+ .bindings_for_action(action)
+ }
}
impl Context for WindowContext<'_> {
@@ -1,50 +1,42 @@
-use std::collections::HashSet;
-
-use strum::{EnumIter, IntoEnumIterator};
+use gpui::Action;
+use strum::EnumIter;
use crate::prelude::*;
#[derive(Component)]
-pub struct Keybinding {
+pub struct KeyBinding {
/// A keybinding consists of a key and a set of modifier keys.
/// More then one keybinding produces a chord.
///
/// This should always contain at least one element.
- keybinding: Vec<(String, ModifierKeys)>,
+ key_binding: gpui::KeyBinding,
}
-impl Keybinding {
- pub fn new(key: String, modifiers: ModifierKeys) -> Self {
- Self {
- keybinding: vec![(key, modifiers)],
- }
+impl KeyBinding {
+ pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
+ // todo! this last is arbitrary, we want to prefer users key bindings over defaults,
+ // and vim over normal (in vim mode), etc.
+ let key_binding = cx.bindings_for_action(action).last().cloned()?;
+ Some(Self::new(key_binding))
}
- pub fn new_chord(
- first_note: (String, ModifierKeys),
- second_note: (String, ModifierKeys),
- ) -> Self {
- Self {
- keybinding: vec![first_note, second_note],
- }
+ pub fn new(key_binding: gpui::KeyBinding) -> Self {
+ Self { key_binding }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.flex()
.gap_2()
- .children(self.keybinding.iter().map(|(key, modifiers)| {
+ .children(self.key_binding.keystrokes().iter().map(|keystroke| {
div()
.flex()
.gap_1()
- .children(ModifierKey::iter().filter_map(|modifier| {
- if modifiers.0.contains(&modifier) {
- Some(Key::new(modifier.glyph().to_string()))
- } else {
- None
- }
- }))
- .child(Key::new(key.clone()))
+ .when(keystroke.modifiers.control, |el| el.child(Key::new("^")))
+ .when(keystroke.modifiers.alt, |el| el.child(Key::new("⌥")))
+ .when(keystroke.modifiers.command, |el| el.child(Key::new("⌘")))
+ .when(keystroke.modifiers.shift, |el| el.child(Key::new("⇧")))
+ .child(Key::new(keystroke.key.clone()))
}))
}
}
@@ -81,76 +73,6 @@ pub enum ModifierKey {
Shift,
}
-impl ModifierKey {
- /// Returns the glyph for the [`ModifierKey`].
- pub fn glyph(&self) -> char {
- match self {
- Self::Control => '^',
- Self::Alt => '⌥',
- Self::Command => '⌘',
- Self::Shift => '⇧',
- }
- }
-}
-
-#[derive(Clone)]
-pub struct ModifierKeys(HashSet<ModifierKey>);
-
-impl ModifierKeys {
- pub fn new() -> Self {
- Self(HashSet::new())
- }
-
- pub fn all() -> Self {
- Self(HashSet::from_iter(ModifierKey::iter()))
- }
-
- pub fn add(mut self, modifier: ModifierKey) -> Self {
- self.0.insert(modifier);
- self
- }
-
- pub fn control(mut self, control: bool) -> Self {
- if control {
- self.0.insert(ModifierKey::Control);
- } else {
- self.0.remove(&ModifierKey::Control);
- }
-
- self
- }
-
- pub fn alt(mut self, alt: bool) -> Self {
- if alt {
- self.0.insert(ModifierKey::Alt);
- } else {
- self.0.remove(&ModifierKey::Alt);
- }
-
- self
- }
-
- pub fn command(mut self, command: bool) -> Self {
- if command {
- self.0.insert(ModifierKey::Command);
- } else {
- self.0.remove(&ModifierKey::Command);
- }
-
- self
- }
-
- pub fn shift(mut self, shift: bool) -> Self {
- if shift {
- self.0.insert(ModifierKey::Shift);
- } else {
- self.0.remove(&ModifierKey::Shift);
- }
-
- self
- }
-}
-
#[cfg(feature = "stories")]
pub use stories::*;
@@ -158,29 +80,38 @@ pub use stories::*;
mod stories {
use super::*;
use crate::Story;
- use gpui::{Div, Render};
+ use gpui::{action, Div, Render};
use itertools::Itertools;
pub struct KeybindingStory;
+ #[action]
+ struct NoAction {}
+
+ pub fn binding(key: &str) -> gpui::KeyBinding {
+ gpui::KeyBinding::new(key, NoAction {}, None)
+ }
+
impl Render for KeybindingStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
- let all_modifier_permutations = ModifierKey::iter().permutations(2);
+ let all_modifier_permutations =
+ ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container(cx)
- .child(Story::title_for::<_, Keybinding>(cx))
+ .child(Story::title_for::<_, KeyBinding>(cx))
.child(Story::label(cx, "Single Key"))
- .child(Keybinding::new("Z".to_string(), ModifierKeys::new()))
+ .child(KeyBinding::new(binding("Z")))
.child(Story::label(cx, "Single Key with Modifier"))
.child(
div()
.flex()
.gap_3()
- .children(ModifierKey::iter().map(|modifier| {
- Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier))
- })),
+ .child(KeyBinding::new(binding("ctrl-c")))
+ .child(KeyBinding::new(binding("alt-c")))
+ .child(KeyBinding::new(binding("cmd-c")))
+ .child(KeyBinding::new(binding("shift-c"))),
)
.child(Story::label(cx, "Single Key with Modifier (Permuted)"))
.child(
@@ -194,29 +125,17 @@ mod stories {
.gap_4()
.py_3()
.children(chunk.map(|permutation| {
- let mut modifiers = ModifierKeys::new();
-
- for modifier in permutation {
- modifiers = modifiers.add(modifier);
- }
-
- Keybinding::new("X".to_string(), modifiers)
+ KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
}))
}),
),
)
.child(Story::label(cx, "Single Key with All Modifiers"))
- .child(Keybinding::new("Z".to_string(), ModifierKeys::all()))
+ .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
.child(Story::label(cx, "Chord"))
- .child(Keybinding::new_chord(
- ("A".to_string(), ModifierKeys::new()),
- ("Z".to_string(), ModifierKeys::new()),
- ))
+ .child(KeyBinding::new(binding("a z")))
.child(Story::label(cx, "Chord with Modifier"))
- .child(Keybinding::new_chord(
- ("A".to_string(), ModifierKeys::new().control(true)),
- ("Z".to_string(), ModifierKeys::new().shift(true)),
- ))
+ .child(KeyBinding::new(binding("ctrl-a shift-z")))
}
}
}
@@ -1,5 +1,5 @@
use crate::prelude::*;
-use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
+use crate::{h_stack, v_stack, KeyBinding, Label, LabelColor};
#[derive(Component)]
pub struct Palette {
@@ -108,7 +108,7 @@ impl Palette {
pub struct PaletteItem {
pub label: SharedString,
pub sublabel: Option<SharedString>,
- pub keybinding: Option<Keybinding>,
+ pub keybinding: Option<KeyBinding>,
}
impl PaletteItem {
@@ -132,7 +132,7 @@ impl PaletteItem {
pub fn keybinding<K>(mut self, keybinding: K) -> Self
where
- K: Into<Option<Keybinding>>,
+ K: Into<Option<KeyBinding>>,
{
self.keybinding = keybinding.into();
self
@@ -161,7 +161,7 @@ pub use stories::*;
mod stories {
use gpui::{Div, Render};
- use crate::{ModifierKeys, Story};
+ use crate::{binding, Story};
use super::*;
@@ -181,46 +181,24 @@ mod stories {
Palette::new("palette-2")
.placeholder("Execute a command...")
.items(vec![
- PaletteItem::new("theme selector: toggle").keybinding(
- Keybinding::new_chord(
- ("k".to_string(), ModifierKeys::new().command(true)),
- ("t".to_string(), ModifierKeys::new().command(true)),
- ),
- ),
- PaletteItem::new("assistant: inline assist").keybinding(
- Keybinding::new(
- "enter".to_string(),
- ModifierKeys::new().command(true),
- ),
- ),
- PaletteItem::new("assistant: quote selection").keybinding(
- Keybinding::new(
- ">".to_string(),
- ModifierKeys::new().command(true),
- ),
- ),
- PaletteItem::new("assistant: toggle focus").keybinding(
- Keybinding::new(
- "?".to_string(),
- ModifierKeys::new().command(true),
- ),
- ),
+ PaletteItem::new("theme selector: toggle")
+ .keybinding(KeyBinding::new(binding("cmd-k cmd-t"))),
+ PaletteItem::new("assistant: inline assist")
+ .keybinding(KeyBinding::new(binding("cmd-enter"))),
+ PaletteItem::new("assistant: quote selection")
+ .keybinding(KeyBinding::new(binding("cmd-<"))),
+ PaletteItem::new("assistant: toggle focus")
+ .keybinding(KeyBinding::new(binding("cmd-?"))),
PaletteItem::new("auto update: check"),
PaletteItem::new("auto update: view release notes"),
- PaletteItem::new("branches: open recent").keybinding(
- Keybinding::new(
- "b".to_string(),
- ModifierKeys::new().command(true).alt(true),
- ),
- ),
+ PaletteItem::new("branches: open recent")
+ .keybinding(KeyBinding::new(binding("cmd-alt-b"))),
PaletteItem::new("chat panel: toggle focus"),
PaletteItem::new("cli: install"),
PaletteItem::new("client: sign in"),
PaletteItem::new("client: sign out"),
- PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
- "escape".to_string(),
- ModifierKeys::new(),
- )),
+ PaletteItem::new("editor: cancel")
+ .keybinding(KeyBinding::new(binding("escape"))),
]),
)
}
@@ -7,12 +7,12 @@ use gpui::{AppContext, ViewContext};
use rand::Rng;
use theme2::ActiveTheme;
-use crate::HighlightedText;
+use crate::{binding, HighlightedText};
use crate::{
Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
- HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
- MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus,
- PlayerWithCallStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
+ HighlightedLine, Icon, KeyBinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
+ MicStatus, Notification, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus,
+ PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
};
use crate::{ListItem, NotificationAction};
@@ -701,46 +701,16 @@ pub fn static_collab_panel_channels() -> Vec<ListItem> {
pub fn example_editor_actions() -> Vec<PaletteItem> {
vec![
- PaletteItem::new("New File").keybinding(Keybinding::new(
- "N".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Open File").keybinding(Keybinding::new(
- "O".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Save File").keybinding(Keybinding::new(
- "S".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Cut").keybinding(Keybinding::new(
- "X".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Copy").keybinding(Keybinding::new(
- "C".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Paste").keybinding(Keybinding::new(
- "V".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Undo").keybinding(Keybinding::new(
- "Z".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Redo").keybinding(Keybinding::new(
- "Z".to_string(),
- ModifierKeys::new().command(true).shift(true),
- )),
- PaletteItem::new("Find").keybinding(Keybinding::new(
- "F".to_string(),
- ModifierKeys::new().command(true),
- )),
- PaletteItem::new("Replace").keybinding(Keybinding::new(
- "R".to_string(),
- ModifierKeys::new().command(true),
- )),
+ PaletteItem::new("New File").keybinding(KeyBinding::new(binding("cmd-n"))),
+ PaletteItem::new("Open File").keybinding(KeyBinding::new(binding("cmd-o"))),
+ PaletteItem::new("Save File").keybinding(KeyBinding::new(binding("cmd-s"))),
+ PaletteItem::new("Cut").keybinding(KeyBinding::new(binding("cmd-x"))),
+ PaletteItem::new("Copy").keybinding(KeyBinding::new(binding("cmd-c"))),
+ PaletteItem::new("Paste").keybinding(KeyBinding::new(binding("cmd-v"))),
+ PaletteItem::new("Undo").keybinding(KeyBinding::new(binding("cmd-z"))),
+ PaletteItem::new("Redo").keybinding(KeyBinding::new(binding("cmd-shift-z"))),
+ PaletteItem::new("Find").keybinding(KeyBinding::new(binding("cmd-f"))),
+ PaletteItem::new("Replace").keybinding(KeyBinding::new(binding("cmd-r"))),
PaletteItem::new("Jump to Line"),
PaletteItem::new("Select All"),
PaletteItem::new("Deselect All"),