use super::class::parse_single_class;
use super::tables::{COLOR_FAMILIES, COLOR_SHADES, lookup_color};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::cell::RefCell;
thread_local! {
static COMMON_CLASS_MATCHES_STR: RefCell<Option<String>> = const { RefCell::new(None) };
static NUMERIC_FALLBACK_STR: RefCell<Option<String>> = const { RefCell::new(None) };
}
fn get_cached_common_class_matches() -> TokenStream {
COMMON_CLASS_MATCHES_STR.with(|cell| {
let mut borrow = cell.borrow_mut();
let s = borrow.get_or_insert_with(|| {
generate_common_class_matches()
.into_iter()
.map(|ts| ts.to_string())
.collect::<String>()
});
s.parse::<TokenStream>()
.expect("cached match arms are valid")
})
}
fn get_cached_numeric_fallback() -> TokenStream {
NUMERIC_FALLBACK_STR.with(|cell| {
let mut borrow = cell.borrow_mut();
let s = borrow.get_or_insert_with(|| generate_numeric_fallback_code().to_string());
s.parse::<TokenStream>()
.expect("cached numeric fallback is valid")
})
}
pub(crate) fn generate_dynamic_class_code(class_expr: &syn::Expr) -> TokenStream {
let common_classes = get_cached_common_class_matches();
let numeric_fallbacks = get_cached_numeric_fallback();
quote! {
{
#[inline(never)]
fn __rsx_apply_class<E: Styled>(el: E, class: &str) -> E {
match class {
#common_classes
_ => {
#numeric_fallbacks
#[cfg(debug_assertions)]
if !class.is_empty() {
eprintln!(
"[gpui-rsx] warning: 动态 class {:?} 被忽略(不支持的 class 类型)\n \
提示:改用字符串字面量 class=\"{}\" 可支持所有 class",
class, class
);
}
el
}
}
}
let __class_expr = #class_expr;
let __class_str: &str = __class_expr.as_ref();
if __class_str.is_empty() {
__el
} else {
__class_str.split_ascii_whitespace().fold(__el, __rsx_apply_class)
}
}
}
}
fn generate_numeric_fallback_code() -> TokenStream {
quote! {
if let Some(rest) = class.strip_prefix("gap-x-") {
if let Ok(n) = rest.parse::<f32>() { return el.gap_x(px(n)); }
}
if let Some(rest) = class.strip_prefix("gap-y-") {
if let Ok(n) = rest.parse::<f32>() { return el.gap_y(px(n)); }
}
if let Some(rest) = class.strip_prefix("gap-") {
if let Ok(n) = rest.parse::<f32>() { return el.gap(px(n)); }
}
if let Some(rest) = class.strip_prefix("px-") {
if let Ok(n) = rest.parse::<f32>() { return el.px(px(n)); }
}
if let Some(rest) = class.strip_prefix("py-") {
if let Ok(n) = rest.parse::<f32>() { return el.py(px(n)); }
}
if let Some(rest) = class.strip_prefix("pt-") {
if let Ok(n) = rest.parse::<f32>() { return el.pt(px(n)); }
}
if let Some(rest) = class.strip_prefix("pb-") {
if let Ok(n) = rest.parse::<f32>() { return el.pb(px(n)); }
}
if let Some(rest) = class.strip_prefix("pl-") {
if let Ok(n) = rest.parse::<f32>() { return el.pl(px(n)); }
}
if let Some(rest) = class.strip_prefix("pr-") {
if let Ok(n) = rest.parse::<f32>() { return el.pr(px(n)); }
}
if let Some(rest) = class.strip_prefix("p-") {
if let Ok(n) = rest.parse::<f32>() { return el.p(px(n)); }
}
if let Some(rest) = class.strip_prefix("mx-") {
if let Ok(n) = rest.parse::<f32>() { return el.mx(px(n)); }
}
if let Some(rest) = class.strip_prefix("my-") {
if let Ok(n) = rest.parse::<f32>() { return el.my(px(n)); }
}
if let Some(rest) = class.strip_prefix("mt-") {
if let Ok(n) = rest.parse::<f32>() { return el.mt(px(n)); }
}
if let Some(rest) = class.strip_prefix("mb-") {
if let Ok(n) = rest.parse::<f32>() { return el.mb(px(n)); }
}
if let Some(rest) = class.strip_prefix("ml-") {
if let Ok(n) = rest.parse::<f32>() { return el.ml(px(n)); }
}
if let Some(rest) = class.strip_prefix("mr-") {
if let Ok(n) = rest.parse::<f32>() { return el.mr(px(n)); }
}
if let Some(rest) = class.strip_prefix("m-") {
if let Ok(n) = rest.parse::<f32>() { return el.m(px(n)); }
}
if let Some(rest) = class.strip_prefix("min-w-") {
if let Ok(n) = rest.parse::<f32>() { return el.min_w(px(n)); }
}
if let Some(rest) = class.strip_prefix("max-w-") {
if let Ok(n) = rest.parse::<f32>() { return el.max_w(px(n)); }
}
if let Some(rest) = class.strip_prefix("min-h-") {
if let Ok(n) = rest.parse::<f32>() { return el.min_h(px(n)); }
}
if let Some(rest) = class.strip_prefix("max-h-") {
if let Ok(n) = rest.parse::<f32>() { return el.max_h(px(n)); }
}
if let Some(rest) = class.strip_prefix("size-") {
if let Ok(n) = rest.parse::<f32>() { return el.size(px(n)); }
}
if let Some(rest) = class.strip_prefix("w-") {
if let Ok(n) = rest.parse::<f32>() { return el.w(px(n)); }
}
if let Some(rest) = class.strip_prefix("h-") {
if let Ok(n) = rest.parse::<f32>() { return el.h(px(n)); }
}
if let Some(rest) = class.strip_prefix("opacity-") {
if let Ok(n) = rest.parse::<f32>() { return el.opacity(n / 100.0); }
}
if let Some(rest) = class.strip_prefix("z-") {
if let Ok(n) = rest.parse::<i32>() { return el.z_index(n); }
}
if let Some(rest) = class.strip_prefix("col-span-") {
if let Ok(n) = rest.parse::<u16>() { return el.col_span(n); }
}
if let Some(rest) = class.strip_prefix("col-start-") {
if let Ok(n) = rest.parse::<i16>() { return el.col_start(n); }
}
if let Some(rest) = class.strip_prefix("col-end-") {
if let Ok(n) = rest.parse::<i16>() { return el.col_end(n); }
}
if let Some(rest) = class.strip_prefix("row-span-") {
if let Ok(n) = rest.parse::<u16>() { return el.row_span(n); }
}
if let Some(rest) = class.strip_prefix("row-start-") {
if let Ok(n) = rest.parse::<i16>() { return el.row_start(n); }
}
if let Some(rest) = class.strip_prefix("row-end-") {
if let Ok(n) = rest.parse::<i16>() { return el.row_end(n); }
}
if let Some(rest) = class.strip_prefix("grid-cols-") {
if let Ok(n) = rest.parse::<u16>() { return el.grid_cols(n); }
}
if let Some(rest) = class.strip_prefix("grid-rows-") {
if let Ok(n) = rest.parse::<u16>() { return el.grid_rows(n); }
}
}
}
fn generate_common_class_matches() -> Vec<TokenStream> {
let static_classes = [
"flex",
"flex-col",
"flex-col-reverse",
"flex-row",
"flex-row-reverse",
"flex-1",
"flex-auto",
"flex-initial",
"flex-none",
"flex-wrap",
"flex-wrap-reverse",
"flex-nowrap",
"flex-shrink-0",
"block",
"grid",
"hidden",
"items-center",
"items-start",
"items-end",
"items-baseline",
"items-stretch",
"justify-center",
"justify-between",
"justify-start",
"justify-end",
"justify-around",
"justify-evenly",
"content-center",
"content-start",
"content-end",
"content-between",
"content-around",
"content-evenly",
"content-stretch",
"gap-1",
"gap-2",
"gap-3",
"gap-4",
"gap-5",
"gap-6",
"gap-8",
"gap-10",
"gap-12",
"p-1",
"p-2",
"p-3",
"p-4",
"p-5",
"p-6",
"p-8",
"px-1",
"px-2",
"px-3",
"px-4",
"px-6",
"py-1",
"py-2",
"py-3",
"py-4",
"py-6",
"pt-1",
"pt-2",
"pt-4",
"pt-6",
"pb-1",
"pb-2",
"pb-4",
"pb-6",
"pl-2",
"pl-4",
"pr-2",
"pr-4",
"m-1",
"m-2",
"m-4",
"mx-1",
"mx-2",
"mx-4",
"my-1",
"my-2",
"my-4",
"mt-1",
"mt-2",
"mt-4",
"mb-1",
"mb-2",
"mb-4",
"w-full",
"h-full",
"size-full",
"text-xs",
"text-sm",
"text-base",
"text-lg",
"text-xl",
"text-2xl",
"text-3xl",
"text-left",
"text-center",
"text-right",
"truncate",
"text-ellipsis",
"italic",
"not-italic",
"underline",
"line-through",
"font-bold",
"border",
"border-2",
"border-dashed",
"border-t",
"border-b",
"border-l",
"border-r",
"rounded-none",
"rounded-sm",
"rounded-md",
"rounded-lg",
"rounded-xl",
"rounded-full",
"cursor-pointer",
"cursor-default",
"cursor-text",
"overflow-hidden",
"overflow-scroll",
"overflow-visible",
"absolute",
"relative",
"shadow-sm",
"shadow-md",
"shadow-lg",
"opacity-0",
"opacity-25",
"opacity-50",
"opacity-75",
"opacity-100",
"z-0",
"z-10",
"z-20",
"z-50",
];
const COLOR_ENTRIES: usize = 22 * 11 * 3 + 6;
let mut matches = Vec::with_capacity(static_classes.len() + COLOR_ENTRIES);
for class_str in static_classes {
let method_call = parse_single_class(class_str);
matches.push(quote! {
#class_str => el #method_call,
});
}
let families = COLOR_FAMILIES;
let shades = COLOR_SHADES;
let text_color_ident = syn::Ident::new("text_color", Span::call_site());
let bg_ident = syn::Ident::new("bg", Span::call_site());
let border_color_ident = syn::Ident::new("border_color", Span::call_site());
for family in families {
for shade in shades {
let color_key = format!("{family}_{shade}");
if let Some(hex) = lookup_color(&color_key) {
let text_class = format!("text-{family}-{shade}");
let bg_class = format!("bg-{family}-{shade}");
let border_class = format!("border-{family}-{shade}");
matches.push(quote! { #text_class => el.#text_color_ident(rgb(#hex)), });
matches.push(quote! { #bg_class => el.#bg_ident(rgb(#hex)), });
matches.push(quote! { #border_class => el.#border_color_ident(rgb(#hex)), });
}
}
}
for (class_str, method_ident, hex) in [
("text-black", &text_color_ident, 0x000000u32),
("text-white", &text_color_ident, 0xffffff_u32),
("bg-black", &bg_ident, 0x000000_u32),
("bg-white", &bg_ident, 0xffffff_u32),
("border-black", &border_color_ident, 0x000000_u32),
("border-white", &border_color_ident, 0xffffff_u32),
] {
matches.push(quote! { #class_str => el.#method_ident(rgb(#hex)), });
}
matches
}