use super::class::parse_single_class;
use super::tables::{COLOR_FAMILIES, COLOR_SHADES, lookup_color};
use proc_macro2::TokenStream;
use quote::quote;
use std::cell::RefCell;
thread_local! {
static COMMON_CLASS_MATCHES_STR: RefCell<Option<String>> = const { RefCell::new(None) };
static COLOR_FALLBACK_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_color_fallback() -> TokenStream {
COLOR_FALLBACK_STR.with(|cell| {
let mut borrow = cell.borrow_mut();
let s = borrow.get_or_insert_with(|| generate_color_fallback_code().to_string());
s.parse::<TokenStream>()
.expect("cached color fallback is 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 color_fallbacks = get_cached_color_fallback();
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
_ => {
#color_fallbacks
#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_color_fallback_code() -> TokenStream {
let shade_arms = COLOR_SHADES.iter().enumerate().map(|(idx, shade)| {
quote! { #shade => #idx, }
});
let family_arms = COLOR_FAMILIES.iter().map(|family| {
let values = COLOR_SHADES.iter().map(|shade| {
let key = format!("{family}_{shade}");
lookup_color(&key).expect("COLOR_FAMILIES/COLOR_SHADES must match lookup_color")
});
quote! {
#family => {
const VALUES: [u32; 11] = [#(#values),*];
Some(VALUES[shade_index])
}
}
});
quote! {
fn __rsx_hex_digit(byte: u8) -> Option<u32> {
match byte {
b'0'..=b'9' => Some((byte - b'0') as u32),
b'a'..=b'f' => Some((byte - b'a' + 10) as u32),
b'A'..=b'F' => Some((byte - b'A' + 10) as u32),
_ => None,
}
}
fn __rsx_parse_hex_color(color: &str) -> Option<u32> {
let inner = color.strip_prefix("[#")?.strip_suffix(']')?;
let bytes = inner.as_bytes();
match bytes.len() {
6 => u32::from_str_radix(inner, 16).ok(),
3 => {
let r = __rsx_hex_digit(bytes[0])?;
let g = __rsx_hex_digit(bytes[1])?;
let b = __rsx_hex_digit(bytes[2])?;
Some(r << 20 | r << 16 | g << 12 | g << 8 | b << 4 | b)
}
_ => None,
}
}
fn __rsx_parse_named_color(color: &str) -> Option<u32> {
if color == "black" {
return Some(0x000000);
}
if color == "white" {
return Some(0xffffff);
}
let (family, shade) = color.rsplit_once('-')?;
let shade_index = match shade {
#(#shade_arms)*
_ => return None,
};
match family {
#(#family_arms,)*
_ => None,
}
}
fn __rsx_parse_color(color: &str) -> Option<u32> {
__rsx_parse_hex_color(color).or_else(|| __rsx_parse_named_color(color))
}
if let Some(rest) = class.strip_prefix("text-")
&& let Some(color) = __rsx_parse_color(rest)
{
return el.text_color(rgb(color));
}
if let Some(rest) = class.strip_prefix("bg-")
&& let Some(color) = __rsx_parse_color(rest)
{
return el.bg(rgb(color));
}
if let Some(rest) = class.strip_prefix("border-")
&& let Some(color) = __rsx_parse_color(rest)
{
return el.border_color(rgb(color));
}
}
}
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("line-clamp-") {
if let Ok(n) = rest.parse::<usize>() { return el.line_clamp(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-grow",
"flex-grow-0",
"flex-wrap",
"flex-wrap-reverse",
"flex-nowrap",
"flex-shrink",
"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-normal",
"content-center",
"content-start",
"content-end",
"content-between",
"content-around",
"content-evenly",
"content-stretch",
"self-start",
"self-end",
"self-flex-start",
"self-flex-end",
"self-center",
"self-baseline",
"self-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",
"aspect-square",
"text-xs",
"text-sm",
"text-base",
"text-lg",
"text-xl",
"text-2xl",
"text-3xl",
"text-left",
"text-center",
"text-right",
"whitespace-normal",
"whitespace-nowrap",
"truncate",
"text-ellipsis",
"text-ellipsis-start",
"no-underline",
"italic",
"not-italic",
"underline",
"line-through",
"text-decoration-solid",
"text-decoration-wavy",
"text-decoration-0",
"text-decoration-1",
"text-decoration-2",
"text-decoration-4",
"text-decoration-8",
"font-bold",
"border",
"border-2",
"border-dashed",
"border-t",
"border-b",
"border-l",
"border-r",
"border-x",
"border-y",
"border-t-2",
"border-b-2",
"border-l-2",
"border-r-2",
"border-x-2",
"border-y-2",
"rounded-none",
"rounded-sm",
"rounded-md",
"rounded-lg",
"rounded-xl",
"rounded-full",
"cursor-pointer",
"cursor-default",
"cursor-text",
"cursor-move",
"cursor-not-allowed",
"cursor-context-menu",
"cursor-crosshair",
"cursor-vertical-text",
"cursor-alias",
"cursor-copy",
"cursor-no-drop",
"cursor-grab",
"cursor-grabbing",
"cursor-ew-resize",
"cursor-ns-resize",
"cursor-nesw-resize",
"cursor-nwse-resize",
"cursor-col-resize",
"cursor-row-resize",
"cursor-n-resize",
"cursor-e-resize",
"cursor-s-resize",
"cursor-w-resize",
"overflow-hidden",
"overflow-x-hidden",
"overflow-y-hidden",
"absolute",
"relative",
"shadow-none",
"shadow-2xs",
"shadow-xs",
"shadow-sm",
"shadow-md",
"shadow-lg",
"shadow-xl",
"shadow-2xl",
"opacity-0",
"opacity-25",
"opacity-50",
"opacity-75",
"opacity-100",
"col-span-full",
"col-start-auto",
"col-end-auto",
"row-span-full",
"row-start-auto",
"row-end-auto",
];
let mut matches = Vec::with_capacity(static_classes.len());
for class_str in static_classes {
let method_call = parse_single_class(class_str);
matches.push(quote! {
#class_str => el #method_call,
});
}
matches
}