zeta: Improve UX for simultaneous LSP and prediction completions (#24024)

Release Notes:

- N/A

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Danilo <danilo@zed.dev>
Co-authored-by: Richard <richard@zed.dev>
This commit is contained in:
Agus Zubiaga 2025-02-03 21:47:11 -03:00 committed by GitHub
parent b6e680ea3d
commit 4c29e1ff07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1196 additions and 762 deletions

View file

@ -1,7 +1,9 @@
#![allow(missing_docs)]
use crate::PlatformStyle;
use crate::{h_flex, prelude::*, Icon, IconName, IconSize};
use gpui::{relative, Action, App, FocusHandle, IntoElement, Keystroke, Window};
use gpui::{
relative, Action, AnyElement, App, FocusHandle, IntoElement, Keystroke, Modifiers, Window,
};
#[derive(Debug, IntoElement, Clone)]
pub struct KeyBinding {
@ -41,30 +43,6 @@ impl KeyBinding {
Some(Self::new(key_binding))
}
fn icon_for_key(&self, keystroke: &Keystroke) -> Option<IconName> {
match keystroke.key.as_str() {
"left" => Some(IconName::ArrowLeft),
"right" => Some(IconName::ArrowRight),
"up" => Some(IconName::ArrowUp),
"down" => Some(IconName::ArrowDown),
"backspace" => Some(IconName::Backspace),
"delete" => Some(IconName::Delete),
"return" => Some(IconName::Return),
"enter" => Some(IconName::Return),
"tab" => Some(IconName::Tab),
"space" => Some(IconName::Space),
"escape" => Some(IconName::Escape),
"pagedown" => Some(IconName::PageDown),
"pageup" => Some(IconName::PageUp),
"shift" if self.platform_style == PlatformStyle::Mac => Some(IconName::Shift),
"control" if self.platform_style == PlatformStyle::Mac => Some(IconName::Control),
"platform" if self.platform_style == PlatformStyle::Mac => Some(IconName::Command),
"function" if self.platform_style == PlatformStyle::Mac => Some(IconName::Control),
"alt" if self.platform_style == PlatformStyle::Mac => Some(IconName::Option),
_ => None,
}
}
pub fn new(key_binding: gpui::KeyBinding) -> Self {
Self {
key_binding,
@ -96,63 +74,148 @@ impl RenderOnce for KeyBinding {
.gap(DynamicSpacing::Base04.rems(cx))
.flex_none()
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
let key_icon = self.icon_for_key(keystroke);
h_flex()
.flex_none()
.py_0p5()
.rounded_sm()
.text_color(cx.theme().colors().text_muted)
.when(keystroke.modifiers.function, |el| {
match self.platform_style {
PlatformStyle::Mac => el.child(Key::new("fn")),
PlatformStyle::Linux | PlatformStyle::Windows => {
el.child(Key::new("Fn")).child(Key::new("+"))
}
}
})
.when(keystroke.modifiers.control, |el| {
match self.platform_style {
PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Control)),
PlatformStyle::Linux | PlatformStyle::Windows => {
el.child(Key::new("Ctrl")).child(Key::new("+"))
}
}
})
.when(keystroke.modifiers.alt, |el| match self.platform_style {
PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Option)),
PlatformStyle::Linux | PlatformStyle::Windows => {
el.child(Key::new("Alt")).child(Key::new("+"))
}
})
.when(keystroke.modifiers.platform, |el| {
match self.platform_style {
PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Command)),
PlatformStyle::Linux => {
el.child(Key::new("Super")).child(Key::new("+"))
}
PlatformStyle::Windows => {
el.child(Key::new("Win")).child(Key::new("+"))
}
}
})
.when(keystroke.modifiers.shift, |el| match self.platform_style {
PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Shift)),
PlatformStyle::Linux | PlatformStyle::Windows => {
el.child(Key::new("Shift")).child(Key::new("+"))
}
})
.map(|el| match key_icon {
Some(icon) => el.child(KeyIcon::new(icon)),
None => el.child(Key::new(keystroke.key.to_uppercase())),
})
.children(render_modifiers(
&keystroke.modifiers,
self.platform_style,
None,
))
.map(|el| el.child(render_key(&keystroke, self.platform_style, None)))
}))
}
}
pub fn render_key(
keystroke: &Keystroke,
platform_style: PlatformStyle,
color: Option<Color>,
) -> AnyElement {
let key_icon = icon_for_key(keystroke, platform_style);
match key_icon {
Some(icon) => KeyIcon::new(icon, color).into_any_element(),
None => Key::new(
if keystroke.key.len() > 1 {
keystroke.key.clone()
} else {
keystroke.key.to_uppercase()
},
color,
)
.into_any_element(),
}
}
fn icon_for_key(keystroke: &Keystroke, platform_style: PlatformStyle) -> Option<IconName> {
match keystroke.key.as_str() {
"left" => Some(IconName::ArrowLeft),
"right" => Some(IconName::ArrowRight),
"up" => Some(IconName::ArrowUp),
"down" => Some(IconName::ArrowDown),
"backspace" => Some(IconName::Backspace),
"delete" => Some(IconName::Delete),
"return" => Some(IconName::Return),
"enter" => Some(IconName::Return),
// "tab" => Some(IconName::Tab),
"space" => Some(IconName::Space),
"escape" => Some(IconName::Escape),
"pagedown" => Some(IconName::PageDown),
"pageup" => Some(IconName::PageUp),
"shift" if platform_style == PlatformStyle::Mac => Some(IconName::Shift),
"control" if platform_style == PlatformStyle::Mac => Some(IconName::Control),
"platform" if platform_style == PlatformStyle::Mac => Some(IconName::Command),
"function" if platform_style == PlatformStyle::Mac => Some(IconName::Control),
"alt" if platform_style == PlatformStyle::Mac => Some(IconName::Option),
_ => None,
}
}
pub fn render_modifiers(
modifiers: &Modifiers,
platform_style: PlatformStyle,
color: Option<Color>,
) -> impl Iterator<Item = AnyElement> {
enum KeyOrIcon {
Key(&'static str),
Icon(IconName),
}
struct Modifier {
enabled: bool,
mac: KeyOrIcon,
linux: KeyOrIcon,
windows: KeyOrIcon,
}
let table = {
use KeyOrIcon::*;
[
Modifier {
enabled: modifiers.function,
mac: Icon(IconName::Control),
linux: Key("Fn"),
windows: Key("Fn"),
},
Modifier {
enabled: modifiers.control,
mac: Icon(IconName::Control),
linux: Key("Ctrl"),
windows: Key("Ctrl"),
},
Modifier {
enabled: modifiers.alt,
mac: Icon(IconName::Option),
linux: Key("Alt"),
windows: Key("Alt"),
},
Modifier {
enabled: modifiers.platform,
mac: Icon(IconName::Command),
linux: Key("Super"),
windows: Key("Win"),
},
Modifier {
enabled: modifiers.shift,
mac: Icon(IconName::Shift),
linux: Key("Shift"),
windows: Key("Shift"),
},
]
};
table
.into_iter()
.flat_map(move |modifier| {
if modifier.enabled {
match platform_style {
PlatformStyle::Mac => Some(modifier.mac),
PlatformStyle::Linux => Some(modifier.linux)
.into_iter()
.chain(Some(KeyOrIcon::Key("+")))
.next(),
PlatformStyle::Windows => Some(modifier.windows)
.into_iter()
.chain(Some(KeyOrIcon::Key("+")))
.next(),
}
} else {
None
}
})
.map(move |key_or_icon| match key_or_icon {
KeyOrIcon::Key(key) => Key::new(key, color).into_any_element(),
KeyOrIcon::Icon(icon) => KeyIcon::new(icon, color).into_any_element(),
})
}
#[derive(IntoElement)]
pub struct Key {
key: SharedString,
color: Option<Color>,
}
impl RenderOnce for Key {
@ -174,33 +237,37 @@ impl RenderOnce for Key {
.h(rems_from_px(14.))
.text_ui(cx)
.line_height(relative(1.))
.text_color(cx.theme().colors().text_muted)
.text_color(self.color.unwrap_or(Color::Muted).color(cx))
.child(self.key.clone())
}
}
impl Key {
pub fn new(key: impl Into<SharedString>) -> Self {
Self { key: key.into() }
pub fn new(key: impl Into<SharedString>, color: Option<Color>) -> Self {
Self {
key: key.into(),
color,
}
}
}
#[derive(IntoElement)]
pub struct KeyIcon {
icon: IconName,
color: Option<Color>,
}
impl RenderOnce for KeyIcon {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
Icon::new(self.icon)
.size(IconSize::XSmall)
.color(Color::Muted)
.color(self.color.unwrap_or(Color::Muted))
}
}
impl KeyIcon {
pub fn new(icon: IconName) -> Self {
Self { icon }
pub fn new(icon: IconName, color: Option<Color>) -> Self {
Self { icon, color }
}
}