Files
Ely-GPUI-Components/src/settings/syntax.rs
T
ZacharyZhang-NY 5f61f3e08d Theme · Importing: VS Code themes read into Ely's palette, code palettes to choose
settings::read_vscode_theme reads a VS Code color theme, comments and
trailing commas and all: its type picks the mode, its colors and token
scopes name what they give, and the rest keep Ely's palette of that mode.
settings::ThemeImporter takes one dropped or browsed for, shows its name,
mode and count, and hands it over on Apply; a file that does not read
says why. theme::syntax_themes offers Ely's code colors, Quiet and Paper,
chosen through settings::SyntaxThemePicker, radio cards with a line of
code in each palette. A card's words now shrink as a plain block, so a
preview wraps inside the card at 280px.
2026-09-27 17:11:55 -04:00

116 lines
3.5 KiB
Rust

use std::rc::Rc;
use gpui::{
App, ElementId, FontStyle, HighlightStyle, Hsla, InteractiveElement, IntoElement,
ParentElement, RenderOnce, SharedString, Styled, StyledText, Window, div,
};
use crate::{
forms::RadioCard,
theme::{ActiveTheme, Mode, Radius, Syntax, SyntaxTheme, TextSize},
typography::literal,
};
/// A line of code in `syntax`'s colors.
fn sample(syntax: &Syntax, cx: &App) -> impl IntoElement + use<> {
let theme = cx.theme();
let text = "fn answer() -> u32 { 42 } // truth";
let ink = |color: Hsla| HighlightStyle {
color: Some(color),
..Default::default()
};
let spans = [
(0..2, ink(syntax.keyword)),
(3..9, ink(syntax.function)),
(9..11, ink(syntax.punctuation)),
(12..14, ink(syntax.operator)),
(15..18, ink(syntax.type_name)),
(19..20, ink(syntax.punctuation)),
(21..23, ink(syntax.number)),
(24..25, ink(syntax.punctuation)),
(
26..34,
HighlightStyle {
color: Some(syntax.comment),
font_style: Some(FontStyle::Italic),
..Default::default()
},
),
];
literal(div())
.debug_selector(|| "syntax-sample".into())
.px_3()
.py_2()
.rounded(theme.radius(Radius::Md))
.bg(theme.colors.sunken)
.font_family(theme.mono_family.clone())
.text_size(theme.text_size(TextSize::Sm))
.text_color(theme.colors.fg)
.child(StyledText::new(text).with_highlights(spans))
}
type OnName = Rc<dyn Fn(&'static str, &mut Window, &mut App)>;
/// Code palettes to choose from, each a card with a line of code in its colors for the mode shown.
#[derive(IntoElement)]
pub struct SyntaxThemePicker {
id: ElementId,
themes: Vec<SyntaxTheme>,
selected: SharedString,
on_change: Option<OnName>,
}
impl SyntaxThemePicker {
/// `selected` names one of `themes`.
pub fn new(
id: impl Into<ElementId>,
themes: impl IntoIterator<Item = SyntaxTheme>,
selected: impl Into<SharedString>,
) -> Self {
let themes: Vec<SyntaxTheme> = themes.into_iter().collect();
let selected = selected.into();
assert!(
themes.iter().any(|theme| theme.name == selected.as_ref()),
"syntax theme picker: no theme {selected}"
);
Self {
id: id.into(),
themes,
selected,
on_change: None,
}
}
pub fn on_change(
mut self,
handler: impl Fn(&'static str, &mut Window, &mut App) + 'static,
) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for SyntaxThemePicker {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let id = self.id;
let on_change = self
.on_change
.unwrap_or_else(|| panic!("syntax theme picker {id:?} has no on_change"));
let mode: Mode = cx.theme().mode();
let cards = self.themes.iter().map(|theme| {
let (pick, name) = (on_change.clone(), theme.name);
RadioCard::new(
(id.clone(), format!("theme-{name}")),
self.selected.as_ref() == name,
name,
)
.preview(sample(&theme.of(mode), cx))
.on_select(move |window, cx| {
log::info!("syntax theme picker: {name}");
pick(name, window, cx)
})
});
div().flex().flex_col().gap_2().children(cards)
}
}