Fix linux zeta modifiers display (#24764)
Improves rendering of Zeta keybind shortcuts on Linux Before:  After: (with muting modifier changes merged)  Release Notes: - N/A --------- Co-authored-by: Michael <michael@zed.dev> Co-authored-by: Agus <agus@zed.dev>
This commit is contained in:
parent
522b8d662c
commit
df8adc8b11
6 changed files with 89 additions and 49 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -14414,6 +14414,7 @@ dependencies = [
|
||||||
"strum",
|
"strum",
|
||||||
"theme",
|
"theme",
|
||||||
"ui_macros",
|
"ui_macros",
|
||||||
|
"util",
|
||||||
"windows 0.58.0",
|
"windows 0.58.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,7 @@ use sum_tree::TreeMap;
|
||||||
use text::{BufferId, OffsetUtf16, Rope};
|
use text::{BufferId, OffsetUtf16, Rope};
|
||||||
use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};
|
use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};
|
||||||
use ui::{
|
use ui::{
|
||||||
h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize,
|
h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize, Key,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
};
|
};
|
||||||
use util::{defer, maybe, post_inc, RangeExt, ResultExt, TakeUntilExt, TryFutureExt};
|
use util::{defer, maybe, post_inc, RangeExt, ResultExt, TakeUntilExt, TryFutureExt};
|
||||||
|
@ -5657,29 +5657,39 @@ impl Editor {
|
||||||
fn render_edit_prediction_accept_keybind(&self, window: &mut Window, cx: &App) -> Option<Div> {
|
fn render_edit_prediction_accept_keybind(&self, window: &mut Window, cx: &App) -> Option<Div> {
|
||||||
let accept_binding = self.accept_edit_prediction_keybind(window, cx);
|
let accept_binding = self.accept_edit_prediction_keybind(window, cx);
|
||||||
let accept_keystroke = accept_binding.keystroke()?;
|
let accept_keystroke = accept_binding.keystroke()?;
|
||||||
let colors = cx.theme().colors();
|
|
||||||
let accent_color = colors.text_accent;
|
let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
|
||||||
let editor_bg_color = colors.editor_background;
|
|
||||||
let bg_color = editor_bg_color.blend(accent_color.opacity(0.1));
|
let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
|
||||||
|
Color::Accent
|
||||||
|
} else {
|
||||||
|
Color::Muted
|
||||||
|
};
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.px_0p5()
|
.px_0p5()
|
||||||
.gap_1()
|
.when(is_platform_style_mac, |parent| parent.gap_0p5())
|
||||||
.bg(bg_color)
|
|
||||||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||||
.text_size(TextSize::XSmall.rems(cx))
|
.text_size(TextSize::XSmall.rems(cx))
|
||||||
.children(ui::render_modifiers(
|
.child(h_flex().children(ui::render_modifiers(
|
||||||
&accept_keystroke.modifiers,
|
&accept_keystroke.modifiers,
|
||||||
PlatformStyle::platform(),
|
PlatformStyle::platform(),
|
||||||
Some(if accept_keystroke.modifiers == window.modifiers() {
|
Some(modifiers_color),
|
||||||
Color::Accent
|
|
||||||
} else {
|
|
||||||
Color::Muted
|
|
||||||
}),
|
|
||||||
Some(IconSize::XSmall.rems().into()),
|
Some(IconSize::XSmall.rems().into()),
|
||||||
false,
|
true,
|
||||||
))
|
)))
|
||||||
.child(accept_keystroke.key.clone())
|
.when(is_platform_style_mac, |parent| {
|
||||||
|
parent.child(accept_keystroke.key.clone())
|
||||||
|
})
|
||||||
|
.when(!is_platform_style_mac, |parent| {
|
||||||
|
parent.child(
|
||||||
|
Key::new(
|
||||||
|
util::capitalize(&accept_keystroke.key),
|
||||||
|
Some(Color::Default),
|
||||||
|
)
|
||||||
|
.size(Some(IconSize::XSmall.rems().into())),
|
||||||
|
)
|
||||||
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5808,13 +5818,13 @@ impl Editor {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.child(Label::new("Hold").size(LabelSize::Small))
|
.child(Label::new("Hold").size(LabelSize::Small))
|
||||||
.children(ui::render_modifiers(
|
.child(h_flex().children(ui::render_modifiers(
|
||||||
&accept_keystroke.modifiers,
|
&accept_keystroke.modifiers,
|
||||||
PlatformStyle::platform(),
|
PlatformStyle::platform(),
|
||||||
Some(Color::Default),
|
Some(Color::Default),
|
||||||
Some(IconSize::Small.rems().into()),
|
Some(IconSize::Small.rems().into()),
|
||||||
true,
|
false,
|
||||||
))
|
)))
|
||||||
.into_any(),
|
.into_any(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5858,6 +5868,7 @@ impl Editor {
|
||||||
|
|
||||||
let has_completion = self.active_inline_completion.is_some();
|
let has_completion = self.active_inline_completion.is_some();
|
||||||
|
|
||||||
|
let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
|
||||||
Some(
|
Some(
|
||||||
h_flex()
|
h_flex()
|
||||||
.min_w(min_width)
|
.min_w(min_width)
|
||||||
|
@ -5886,8 +5897,8 @@ impl Editor {
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||||
.gap_1()
|
.when(is_platform_style_mac, |parent| parent.gap_1())
|
||||||
.children(ui::render_modifiers(
|
.child(h_flex().children(ui::render_modifiers(
|
||||||
&accept_keystroke.modifiers,
|
&accept_keystroke.modifiers,
|
||||||
PlatformStyle::platform(),
|
PlatformStyle::platform(),
|
||||||
Some(if !has_completion {
|
Some(if !has_completion {
|
||||||
|
@ -5896,8 +5907,8 @@ impl Editor {
|
||||||
Color::Default
|
Color::Default
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
true,
|
false,
|
||||||
)),
|
))),
|
||||||
)
|
)
|
||||||
.child(Label::new("Preview").into_any_element())
|
.child(Label::new("Preview").into_any_element())
|
||||||
.opacity(if has_completion { 1.0 } else { 0.4 }),
|
.opacity(if has_completion { 1.0 } else { 0.4 }),
|
||||||
|
|
|
@ -3815,7 +3815,7 @@ impl EditorElement {
|
||||||
let mut element = h_flex()
|
let mut element = h_flex()
|
||||||
.items_start()
|
.items_start()
|
||||||
.child(
|
.child(
|
||||||
div()
|
h_flex()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.border(BORDER_WIDTH)
|
.border(BORDER_WIDTH)
|
||||||
.shadow_sm()
|
.shadow_sm()
|
||||||
|
|
|
@ -16,7 +16,7 @@ path = "src/ui.rs"
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
component.workspace = true
|
component.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
itertools = { workspace = true, optional = true }
|
itertools.workspace = true
|
||||||
linkme.workspace = true
|
linkme.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
@ -26,13 +26,14 @@ story = { workspace = true, optional = true }
|
||||||
strum = { workspace = true, features = ["derive"] }
|
strum = { workspace = true, features = ["derive"] }
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
ui_macros.workspace = true
|
ui_macros.workspace = true
|
||||||
|
util.workspace = true
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows.workspace = true
|
windows.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
stories = ["dep:itertools", "dep:story"]
|
stories = ["dep:story"]
|
||||||
|
|
||||||
# cargo-machete doesn't understand that linkme is used in the component macro
|
# cargo-machete doesn't understand that linkme is used in the component macro
|
||||||
[package.metadata.cargo-machete]
|
[package.metadata.cargo-machete]
|
||||||
|
|
|
@ -92,7 +92,7 @@ impl RenderOnce for KeyBinding {
|
||||||
self.platform_style,
|
self.platform_style,
|
||||||
None,
|
None,
|
||||||
self.size,
|
self.size,
|
||||||
false,
|
true,
|
||||||
))
|
))
|
||||||
.map(|el| {
|
.map(|el| {
|
||||||
el.child(render_key(&keystroke, self.platform_style, None, self.size))
|
el.child(render_key(&keystroke, self.platform_style, None, self.size))
|
||||||
|
@ -110,7 +110,7 @@ pub fn render_key(
|
||||||
let key_icon = icon_for_key(keystroke, platform_style);
|
let key_icon = icon_for_key(keystroke, platform_style);
|
||||||
match key_icon {
|
match key_icon {
|
||||||
Some(icon) => KeyIcon::new(icon, color).size(size).into_any_element(),
|
Some(icon) => KeyIcon::new(icon, color).size(size).into_any_element(),
|
||||||
None => Key::new(capitalize(&keystroke.key), color)
|
None => Key::new(util::capitalize(&keystroke.key), color)
|
||||||
.size(size)
|
.size(size)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
}
|
}
|
||||||
|
@ -145,10 +145,12 @@ pub fn render_modifiers(
|
||||||
platform_style: PlatformStyle,
|
platform_style: PlatformStyle,
|
||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
size: Option<AbsoluteLength>,
|
size: Option<AbsoluteLength>,
|
||||||
standalone: bool,
|
trailing_separator: bool,
|
||||||
) -> impl Iterator<Item = AnyElement> {
|
) -> impl Iterator<Item = AnyElement> {
|
||||||
|
#[derive(Clone)]
|
||||||
enum KeyOrIcon {
|
enum KeyOrIcon {
|
||||||
Key(&'static str),
|
Key(&'static str),
|
||||||
|
Plus,
|
||||||
Icon(IconName),
|
Icon(IconName),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,23 +202,34 @@ pub fn render_modifiers(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|modifier| modifier.enabled)
|
.filter(|modifier| modifier.enabled)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let last_ix = filtered.len().saturating_sub(1);
|
|
||||||
|
|
||||||
filtered
|
let platform_keys = filtered
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.map(move |modifier| match platform_style {
|
||||||
.flat_map(move |(ix, modifier)| match platform_style {
|
PlatformStyle::Mac => Some(modifier.mac),
|
||||||
PlatformStyle::Mac => vec![modifier.mac],
|
PlatformStyle::Linux => Some(modifier.linux),
|
||||||
PlatformStyle::Linux if standalone && ix == last_ix => vec![modifier.linux],
|
PlatformStyle::Windows => Some(modifier.windows),
|
||||||
PlatformStyle::Linux => vec![modifier.linux, KeyOrIcon::Key("+")],
|
});
|
||||||
PlatformStyle::Windows if standalone && ix == last_ix => {
|
|
||||||
vec![modifier.windows]
|
let separator = match platform_style {
|
||||||
}
|
PlatformStyle::Mac => None,
|
||||||
PlatformStyle::Windows => vec![modifier.windows, KeyOrIcon::Key("+")],
|
PlatformStyle::Linux => Some(KeyOrIcon::Plus),
|
||||||
|
PlatformStyle::Windows => Some(KeyOrIcon::Plus),
|
||||||
|
};
|
||||||
|
|
||||||
|
let platform_keys = itertools::intersperse(platform_keys, separator.clone());
|
||||||
|
|
||||||
|
platform_keys
|
||||||
|
.chain(if modifiers.modified() && trailing_separator {
|
||||||
|
Some(separator)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
})
|
})
|
||||||
|
.flatten()
|
||||||
.map(move |key_or_icon| match key_or_icon {
|
.map(move |key_or_icon| match key_or_icon {
|
||||||
KeyOrIcon::Key(key) => Key::new(key, color).size(size).into_any_element(),
|
KeyOrIcon::Key(key) => Key::new(key, color).size(size).into_any_element(),
|
||||||
KeyOrIcon::Icon(icon) => KeyIcon::new(icon, color).size(size).into_any_element(),
|
KeyOrIcon::Icon(icon) => KeyIcon::new(icon, color).size(size).into_any_element(),
|
||||||
|
KeyOrIcon::Plus => "+".into_any_element(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,7 +402,7 @@ pub fn text_for_keystroke(keystroke: &Keystroke, platform_style: PlatformStyle)
|
||||||
let key = match keystroke.key.as_str() {
|
let key = match keystroke.key.as_str() {
|
||||||
"pageup" => "PageUp",
|
"pageup" => "PageUp",
|
||||||
"pagedown" => "PageDown",
|
"pagedown" => "PageDown",
|
||||||
key => &capitalize(key),
|
key => &util::capitalize(key),
|
||||||
};
|
};
|
||||||
|
|
||||||
text.push_str(key);
|
text.push_str(key);
|
||||||
|
@ -397,14 +410,6 @@ pub fn text_for_keystroke(keystroke: &Keystroke, platform_style: PlatformStyle)
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capitalize(str: &str) -> String {
|
|
||||||
let mut chars = str.chars();
|
|
||||||
match chars.next() {
|
|
||||||
None => String::new(),
|
|
||||||
Some(first_char) => first_char.to_uppercase().collect::<String>() + chars.as_str(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -787,6 +787,28 @@ impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Capitalizes the first character of a string.
|
||||||
|
///
|
||||||
|
/// This function takes a string slice as input and returns a new `String` with the first character
|
||||||
|
/// capitalized.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use util::capitalize;
|
||||||
|
///
|
||||||
|
/// assert_eq!(capitalize("hello"), "Hello");
|
||||||
|
/// assert_eq!(capitalize("WORLD"), "WORLD");
|
||||||
|
/// assert_eq!(capitalize(""), "");
|
||||||
|
/// ```
|
||||||
|
pub fn capitalize(str: &str) -> String {
|
||||||
|
let mut chars = str.chars();
|
||||||
|
match chars.next() {
|
||||||
|
None => String::new(),
|
||||||
|
Some(first_char) => first_char.to_uppercase().collect::<String>() + chars.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn emoji_regex() -> &'static Regex {
|
fn emoji_regex() -> &'static Regex {
|
||||||
static EMOJI_REGEX: LazyLock<Regex> =
|
static EMOJI_REGEX: LazyLock<Regex> =
|
||||||
LazyLock::new(|| Regex::new("(\\p{Emoji}|\u{200D})").unwrap());
|
LazyLock::new(|| Regex::new("(\\p{Emoji}|\u{200D})").unwrap());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue