Use lowercase for vim key bindings (#25024)

Conrad Irwin created

Vim itself shows keybindings cased according to the character, and so
it's confusing for people coming from vim that we show all key-bindings
uppercase with an icon denoting shift (c.f. #14287).

So, in #24322 we changed Zed to use lowercase for bindings in vim mode.
Unfortunately this (in practice) looks awful because our key icons are
designed to be paired with uppercase letters.

Instead, we now (for vim mode) show the same syntax as would be used in
the keymap file for Zed. This is different from vim (i.e. we show
shift-z instead of Z, and cmd-d instead of <D-z>) but hopefully avoids
the original confusion, and as a bonus makes it easy to figure out the
syntax you need for a workspace::SendKeystrokes.

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/ui/src/components/keybinding.rs | 120 +++++++++++++--------------
1 file changed, 59 insertions(+), 61 deletions(-)

Detailed changes

crates/ui/src/components/keybinding.rs 🔗

@@ -92,15 +92,7 @@ impl KeyBinding {
         match key_icon {
             Some(icon) => KeyIcon::new(icon, color).size(self.size).into_any_element(),
             None => {
-                let key = if self.vim_mode {
-                    if keystroke.modifiers.shift && keystroke.key.len() == 1 {
-                        keystroke.key.to_ascii_uppercase().to_string()
-                    } else {
-                        keystroke.key.to_string()
-                    }
-                } else {
-                    util::capitalize(&keystroke.key)
-                };
+                let key = util::capitalize(&keystroke.key);
                 Key::new(&key, color).size(self.size).into_any_element()
             }
         }
@@ -129,14 +121,25 @@ impl RenderOnce for KeyBinding {
                     .py_0p5()
                     .rounded_sm()
                     .text_color(cx.theme().colors().text_muted)
-                    .children(render_modifiers(
-                        &keystroke.modifiers,
-                        self.platform_style,
-                        None,
-                        self.size,
-                        true,
-                    ))
-                    .map(|el| el.child(self.render_key(&keystroke, None)))
+                    .when(self.vim_mode, |el| {
+                        el.child(
+                            Key::new(
+                                keystroke_text(&keystroke, self.platform_style, self.vim_mode),
+                                None,
+                            )
+                            .size(self.size),
+                        )
+                    })
+                    .when(!self.vim_mode, |el| {
+                        el.children(render_modifiers(
+                            &keystroke.modifiers,
+                            self.platform_style,
+                            None,
+                            self.size,
+                            true,
+                        ))
+                        .map(|el| el.child(self.render_key(&keystroke, None)))
+                    })
             }))
     }
 }
@@ -359,80 +362,75 @@ pub fn text_for_keystroke(keystroke: &Keystroke, cx: &App) -> String {
 }
 
 /// Returns a textual representation of the given [`Keystroke`].
-fn keystroke_text(
-    keystroke: &Keystroke,
-    platform_style: PlatformStyle,
-    vim_enabled: bool,
-) -> String {
+fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode: bool) -> String {
     let mut text = String::new();
 
-    let delimiter = match platform_style {
-        PlatformStyle::Mac => '-',
-        PlatformStyle::Linux | PlatformStyle::Windows => '+',
+    let delimiter = match (platform_style, vim_mode) {
+        (PlatformStyle::Mac, false) => '-',
+        (PlatformStyle::Linux | PlatformStyle::Windows, false) => '+',
+        (_, true) => '-',
     };
 
     if keystroke.modifiers.function {
-        match platform_style {
-            PlatformStyle::Mac => text.push_str("Fn"),
-            PlatformStyle::Linux | PlatformStyle::Windows => text.push_str("Fn"),
+        match vim_mode {
+            false => text.push_str("Fn"),
+            true => text.push_str("fn"),
         }
 
         text.push(delimiter);
     }
 
     if keystroke.modifiers.control {
-        match platform_style {
-            PlatformStyle::Mac => text.push_str("Control"),
-            PlatformStyle::Linux | PlatformStyle::Windows => text.push_str("Ctrl"),
+        match (platform_style, vim_mode) {
+            (PlatformStyle::Mac, false) => text.push_str("Control"),
+            (PlatformStyle::Linux | PlatformStyle::Windows, false) => text.push_str("Ctrl"),
+            (_, true) => text.push_str("ctrl"),
         }
 
         text.push(delimiter);
     }
 
-    if keystroke.modifiers.alt {
-        match platform_style {
-            PlatformStyle::Mac => text.push_str("Option"),
-            PlatformStyle::Linux | PlatformStyle::Windows => text.push_str("Alt"),
+    if keystroke.modifiers.platform {
+        match (platform_style, vim_mode) {
+            (PlatformStyle::Mac, false) => text.push_str("Command"),
+            (PlatformStyle::Mac, true) => text.push_str("cmd"),
+            (PlatformStyle::Linux, false) => text.push_str("Super"),
+            (PlatformStyle::Linux, true) => text.push_str("super"),
+            (PlatformStyle::Windows, false) => text.push_str("Win"),
+            (PlatformStyle::Windows, true) => text.push_str("win"),
         }
 
         text.push(delimiter);
     }
 
-    if keystroke.modifiers.platform {
-        match platform_style {
-            PlatformStyle::Mac => text.push_str("Command"),
-            PlatformStyle::Linux => text.push_str("Super"),
-            PlatformStyle::Windows => text.push_str("Win"),
+    if keystroke.modifiers.alt {
+        match (platform_style, vim_mode) {
+            (PlatformStyle::Mac, false) => text.push_str("Option"),
+            (PlatformStyle::Linux | PlatformStyle::Windows, false) => text.push_str("Alt"),
+            (_, true) => text.push_str("alt"),
         }
 
         text.push(delimiter);
     }
 
     if keystroke.modifiers.shift {
-        if !(vim_enabled && keystroke.key.len() == 1) {
-            match platform_style {
-                PlatformStyle::Mac | PlatformStyle::Linux | PlatformStyle::Windows => {
-                    text.push_str("Shift")
-                }
-            }
-            text.push(delimiter);
+        match (platform_style, vim_mode) {
+            (_, false) => text.push_str("Shift"),
+            (_, true) => text.push_str("shift"),
         }
+        text.push(delimiter);
     }
 
-    let key = match keystroke.key.as_str() {
-        "pageup" => "PageUp",
-        "pagedown" => "PageDown",
-        key if vim_enabled => {
-            if !keystroke.modifiers.shift && key.len() == 1 {
-                key
-            } else {
-                &util::capitalize(key)
-            }
-        }
-        key => &util::capitalize(key),
-    };
-
-    text.push_str(key);
+    if vim_mode {
+        text.push_str(&keystroke.key)
+    } else {
+        let key = match keystroke.key.as_str() {
+            "pageup" => "PageUp",
+            "pagedown" => "PageDown",
+            key => &util::capitalize(key),
+        };
+        text.push_str(key);
+    }
 
     text
 }