gpui-rsx 0.2.2

A JSX-like macro for GPUI - simplify UI development with HTML-like syntax
Documentation
//! Class 字符串解析
//!
//! 将 CSS class 字符串解析为 GPUI 方法调用链,支持:
//! - Tailwind 风格的实用类(flex, gap-4, text-red-500)
//! - 任意 hex 颜色值(bg-[#ff0000])
//! - 间距/尺寸类
//!
//! 核心优化:
//! - 统一的颜色解析函数,避免代码重复
//! - 间距前缀使用 rfind + match(O(1))替代线性扫描(O(17))
//! - text_ 前缀只做一次 strip_prefix,颜色与文本大小分支合并处理
//! - 文本大小使用 match 替代 contains 线性查找
//! - 单遍 `-` → `_` 替换(直接 replace,无需预检 contains)
//! - split_ascii_whitespace 替代 split_whitespace(class 名只含 ASCII)

use super::tables::*;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::borrow::Cow;

/// 解析 class 字符串为方法链片段迭代器
///
/// `"flex flex-col gap-4"` → `[.flex(), .flex_col(), .gap(px(4.0))]`
///
/// 返回迭代器而非 Vec,调用方通过 `extend` 消费时避免中间 Vec 分配。
pub(crate) fn parse_class_string(class_str: &str) -> impl Iterator<Item = TokenStream> + '_ {
    class_str.split_ascii_whitespace().map(parse_single_class)
}

/// 解析单个 CSS class 为方法调用
pub(crate) fn parse_single_class(class: &str) -> TokenStream {
    // 含 '-' 时分配新 String,不含则零拷贝借用原字符串
    let method_name: Cow<str> = if class.contains('-') {
        Cow::Owned(class.replace('-', "_"))
    } else {
        Cow::Borrowed(class)
    };

    // 间距/尺寸类:使用 rfind('_') + match 实现 O(1) 前缀查找
    if let Some(underscore_pos) = method_name.rfind('_') {
        let suffix = &method_name[underscore_pos + 1..];
        if let Ok(num) = suffix.parse::<f32>() {
            let prefix = &method_name[..=underscore_pos];
            if let Some(method) = lookup_spacing_method(prefix) {
                let method_ident = syn::Ident::new(method, Span::call_site());
                return quote! { .#method_ident(px(#num)) };
            }
        }
    }

    // border 特殊处理:
    // "border" (纯) → .border_1()(GPUI 没有无参 .border())
    // "border-2" → .border_2()
    if method_name == "border" {
        return quote! { .border_1() };
    }

    // border-color 类:border-red-500 → .border_color(rgb(0xef4444))
    if let Some(rest) = method_name.strip_prefix("border_") {
        // 方向性边框(border-t/b/l/r 及带数值的方向边框)→ fall through 到默认处理
        let is_directional = matches!(rest, "t" | "b" | "l" | "r")
            || rest.starts_with("t_")
            || rest.starts_with("b_")
            || rest.starts_with("l_")
            || rest.starts_with("r_")
            || rest.starts_with("x_")
            || rest.starts_with("y_");

        if !is_directional {
            if rest.as_bytes().first().is_some_and(|b| b.is_ascii_digit()) {
                // 数值边框宽度 border-2, border-4 等
                let ident = syn::Ident::new(&method_name, Span::call_site());
                return quote! { .#ident() };
            } else if let Some(token) = parse_color_with_method(rest, "border_color") {
                return token;
            }
        }
    }

    // text_ 前缀:统一处理颜色类(text-red-600)和文本大小类(text-xl)
    // 只做一次 strip_prefix("text_"),避免先在颜色分支、再在大小分支各做一次。
    if let Some(rest) = method_name.strip_prefix("text_") {
        // 先查颜色表(text-red-500 → .text_color(rgb(...)))
        if let Some(token) = parse_color_with_method(rest, "text_color") {
            return token;
        }
        // 再查文本大小(text-xl → .text_xl())
        if is_valid_text_size(rest) {
            let size_ident = syn::Ident::new(&method_name, Span::call_site());
            return quote! { .#size_ident() };
        }
        // 不在白名单中的 text_ 前缀,fall through 到默认处理
    }

    // bg_ 颜色类:bg-blue-500 → .bg(rgb(...))
    if let Some(rest) = method_name.strip_prefix("bg_") {
        if let Some(token) = parse_color_with_method(rest, "bg") {
            return token;
        }
    }

    // 默认:无参方法调用
    let ident = syn::Ident::new(&method_name, Span::call_site());
    quote! { .#ident() }
}

/// 统一的颜色解析函数(核心去重逻辑)
///
/// 将颜色名称或任意 hex 值转换为方法调用。
///
/// # 参数
/// - `color`: 颜色字符串(如 "red_500", "[#ff0000]")
/// - `method`: 方法名("text_color", "bg", "border_color")
///
/// # 返回值
/// - `Some(TokenStream)`: 成功解析,返回 `.method(rgb(value))`
/// - `None`: 无法解析颜色
fn parse_color_with_method(color: &str, method: &str) -> Option<TokenStream> {
    // 统一颜色表查找和任意 hex 解析,仅在匹配成功时创建 Ident
    let hex = lookup_color(color).or_else(|| parse_arbitrary_hex(color))?;
    let ident = syn::Ident::new(method, Span::call_site());
    Some(quote! { .#ident(rgb(#hex)) })
}

/// 解析任意 hex 颜色值:`[#rrggbb]` 或 `[#rgb]`
///
/// 输入已经过 `-` → `_` 替换,但 `[#...]` 中不含 `-`,所以保持原样。
/// 返回解析后的 u32 颜色值。
fn parse_arbitrary_hex(s: &str) -> Option<u32> {
    // 匹配 [#rrggbb] 或 [#rgb]
    let inner = s.strip_prefix("[#")?.strip_suffix(']')?;
    match inner.len() {
        6 => u32::from_str_radix(inner, 16).ok(),
        3 => {
            // 3 位 hex 扩展为 6 位: #abc → #aabbcc
            // 用位运算零分配实现,避免 String 堆分配
            let b = inner.as_bytes();
            let d = |c: u8| -> Option<u32> { (c as char).to_digit(16) };
            let r = d(b[0])?;
            let g = d(b[1])?;
            let bl = d(b[2])?;
            // 每个 4-bit 数字复制到高低 nibble: 0xA → 0xAA
            Some(r << 20 | r << 16 | g << 12 | g << 8 | bl << 4 | bl)
        }
        _ => None,
    }
}