style_helpers.rs

  1use proc_macro::TokenStream;
  2use proc_macro2::TokenStream as TokenStream2;
  3use quote::{format_ident, quote};
  4use syn::{
  5    parse::{Parse, ParseStream, Result},
  6    parse_macro_input,
  7};
  8
  9struct StyleableMacroInput;
 10
 11impl Parse for StyleableMacroInput {
 12    fn parse(_input: ParseStream) -> Result<Self> {
 13        Ok(StyleableMacroInput)
 14    }
 15}
 16
 17pub fn style_helpers(input: TokenStream) -> TokenStream {
 18    let _ = parse_macro_input!(input as StyleableMacroInput);
 19    let methods = generate_methods();
 20    let output = quote! {
 21        #(#methods)*
 22    };
 23
 24    output.into()
 25}
 26
 27fn generate_methods() -> Vec<TokenStream2> {
 28    let mut methods = Vec::new();
 29
 30    for (prefix, auto_allowed, fields, prefix_doc_string) in box_prefixes() {
 31        methods.push(generate_custom_value_setter(
 32            prefix,
 33            if auto_allowed {
 34                quote! { Length }
 35            } else {
 36                quote! { DefiniteLength }
 37            },
 38            &fields,
 39            prefix_doc_string,
 40        ));
 41
 42        for (suffix, length_tokens, suffix_doc_string) in box_suffixes() {
 43            if suffix != "auto" || auto_allowed {
 44                methods.push(generate_predefined_setter(
 45                    prefix,
 46                    suffix,
 47                    &fields,
 48                    &length_tokens,
 49                    false,
 50                    &format!("{prefix_doc_string}\n\n{suffix_doc_string}"),
 51                ));
 52            }
 53
 54            if suffix != "auto" {
 55                methods.push(generate_predefined_setter(
 56                    prefix,
 57                    suffix,
 58                    &fields,
 59                    &length_tokens,
 60                    true,
 61                    &format!("{prefix_doc_string}\n\n{suffix_doc_string}"),
 62                ));
 63            }
 64        }
 65    }
 66
 67    for (prefix, fields, prefix_doc_string) in corner_prefixes() {
 68        methods.push(generate_custom_value_setter(
 69            prefix,
 70            quote! { AbsoluteLength },
 71            &fields,
 72            prefix_doc_string,
 73        ));
 74
 75        for (suffix, radius_tokens, suffix_doc_string) in corner_suffixes() {
 76            methods.push(generate_predefined_setter(
 77                prefix,
 78                suffix,
 79                &fields,
 80                &radius_tokens,
 81                false,
 82                &format!("{prefix_doc_string}\n\n{suffix_doc_string}"),
 83            ));
 84        }
 85    }
 86
 87    for (prefix, fields, prefix_doc_string) in border_prefixes() {
 88        methods.push(generate_custom_value_setter(
 89            // The plain method names (e.g., `border`, `border_t`, `border_r`, etc.) are special-cased
 90            // versions of the 1px variants. This better matches Tailwind, but breaks our existing
 91            // convention of the suffix-less variant of the method being the one that accepts a custom value
 92            //
 93            // To work around this, we're assigning a `_width` suffix here.
 94            &format!("{prefix}_width"),
 95            quote! { AbsoluteLength },
 96            &fields,
 97            prefix_doc_string,
 98        ));
 99
100        for (suffix, width_tokens, suffix_doc_string) in border_suffixes() {
101            methods.push(generate_predefined_setter(
102                prefix,
103                suffix,
104                &fields,
105                &width_tokens,
106                false,
107                &format!("{prefix_doc_string}\n\n{suffix_doc_string}"),
108            ));
109        }
110    }
111    methods
112}
113
114fn generate_predefined_setter(
115    name: &'static str,
116    length: &'static str,
117    fields: &[TokenStream2],
118    length_tokens: &TokenStream2,
119    negate: bool,
120    doc_string: &str,
121) -> TokenStream2 {
122    let (negation_prefix, negation_token) = if negate {
123        ("neg_", quote! { - })
124    } else {
125        ("", quote! {})
126    };
127
128    let method_name = if length.is_empty() {
129        format_ident!("{}{}", negation_prefix, name)
130    } else {
131        format_ident!("{}{}_{}", negation_prefix, name, length)
132    };
133
134    let field_assignments = fields
135        .iter()
136        .map(|field_tokens| {
137            quote! {
138                style.#field_tokens = Some((#negation_token gpui::#length_tokens).into());
139            }
140        })
141        .collect::<Vec<_>>();
142
143    let method = quote! {
144        #[doc = #doc_string]
145        fn #method_name(mut self) -> Self {
146            let style = self.style();
147            #(#field_assignments)*
148            self
149        }
150    };
151
152    method
153}
154
155fn generate_custom_value_setter(
156    prefix: &str,
157    length_type: TokenStream2,
158    fields: &[TokenStream2],
159    doc_string: &str,
160) -> TokenStream2 {
161    let method_name = format_ident!("{}", prefix);
162
163    let mut iter = fields.iter();
164    let last = iter.next_back().unwrap();
165    let field_assignments = iter
166        .map(|field_tokens| {
167            quote! {
168                style.#field_tokens = Some(length.clone().into());
169            }
170        })
171        .chain(std::iter::once(quote! {
172            style.#last = Some(length.into());
173        }))
174        .collect::<Vec<_>>();
175
176    let method = quote! {
177        #[doc = #doc_string]
178        fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui::#length_type>) -> Self {
179            let style = self.style();
180            #(#field_assignments)*
181            self
182        }
183    };
184
185    method
186}
187
188/// Returns a vec of (Property name, has 'auto' suffix, tokens for accessing the property, documentation)
189fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>, &'static str)> {
190    vec![
191        (
192            "w",
193            true,
194            vec![quote! { size.width }],
195            "Sets the width of the element. [Docs](https://tailwindcss.com/docs/width)",
196        ),
197        ("h", true, vec![quote! { size.height }], "Sets the height of the element. [Docs](https://tailwindcss.com/docs/height)"),
198        (
199            "size",
200            true,
201            vec![quote! {size.width}, quote! {size.height}],
202            "Sets the width and height of the element."
203        ),
204        // TODO: These don't use the same size ramp as the others
205        // see https://tailwindcss.com/docs/max-width
206        (
207            "min_w",
208            true,
209            vec![quote! { min_size.width }],
210            "Sets the minimum width of the element. [Docs](https://tailwindcss.com/docs/min-width)",
211        ),
212        // TODO: These don't use the same size ramp as the others
213        // see https://tailwindcss.com/docs/max-width
214        (
215            "min_h",
216            true,
217            vec![quote! { min_size.height }],
218            "Sets the minimum height of the element. [Docs](https://tailwindcss.com/docs/min-height)",
219        ),
220        // TODO: These don't use the same size ramp as the others
221        // see https://tailwindcss.com/docs/max-width
222        (
223            "max_w",
224            true,
225            vec![quote! { max_size.width }],
226            "Sets the maximum width of the element. [Docs](https://tailwindcss.com/docs/max-width)",
227        ),
228        // TODO: These don't use the same size ramp as the others
229        // see https://tailwindcss.com/docs/max-width
230        (
231            "max_h",
232            true,
233            vec![quote! { max_size.height }],
234            "Sets the maximum height of the element. [Docs](https://tailwindcss.com/docs/max-height)",
235        ),
236        (
237            "m",
238            true,
239            vec![
240                quote! { margin.top },
241                quote! { margin.bottom },
242                quote! { margin.left },
243                quote! { margin.right },
244            ],
245            "Sets the margin of the element. [Docs](https://tailwindcss.com/docs/margin)"
246        ),
247        ("mt", true, vec![quote! { margin.top }], "Sets the top margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-margin-to-a-single-side)"),
248        (
249            "mb",
250            true,
251            vec![quote! { margin.bottom }],
252            "Sets the bottom margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-margin-to-a-single-side)"
253        ),
254        (
255            "my",
256            true,
257            vec![quote! { margin.top }, quote! { margin.bottom }],
258            "Sets the vertical margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-vertical-margin)"
259        ),
260        (
261            "mx",
262            true,
263            vec![quote! { margin.left }, quote! { margin.right }],
264            "Sets the horizontal margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-horizontal-margin)"
265        ),
266        ("ml", true, vec![quote! { margin.left }], "Sets the left margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-margin-to-a-single-side)"),
267        (
268            "mr",
269            true,
270            vec![quote! { margin.right }],
271            "Sets the right margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-margin-to-a-single-side)"
272        ),
273        (
274            "p",
275            false,
276            vec![
277                quote! { padding.top },
278                quote! { padding.bottom },
279                quote! { padding.left },
280                quote! { padding.right },
281            ],
282            "Sets the padding of the element. [Docs](https://tailwindcss.com/docs/padding)"
283        ),
284        (
285            "pt",
286            false,
287            vec![quote! { padding.top }],
288            "Sets the top padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-padding-to-a-single-side)"
289        ),
290        (
291            "pb",
292            false,
293            vec![quote! { padding.bottom }],
294            "Sets the bottom padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-padding-to-a-single-side)"
295        ),
296        (
297            "px",
298            false,
299            vec![quote! { padding.left }, quote! { padding.right }],
300            "Sets the horizontal padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-horizontal-padding)"
301        ),
302        (
303            "py",
304            false,
305            vec![quote! { padding.top }, quote! { padding.bottom }],
306            "Sets the vertical padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-vertical-padding)"
307        ),
308        (
309            "pl",
310            false,
311            vec![quote! { padding.left }],
312            "Sets the left padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-padding-to-a-single-side)"
313        ),
314        (
315            "pr",
316            false,
317            vec![quote! { padding.right }],
318            "Sets the right padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-padding-to-a-single-side)"
319        ),
320        (
321            "inset",
322            true,
323            vec![quote! { inset.top }, quote! { inset.right }, quote! { inset.bottom }, quote! { inset.left }],
324            "Sets the top, right, bottom, and left values of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
325        ),
326        (
327            "top",
328            true,
329            vec![quote! { inset.top }],
330            "Sets the top value of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
331        ),
332        (
333            "bottom",
334            true,
335            vec![quote! { inset.bottom }],
336            "Sets the bottom value of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
337        ),
338        (
339            "left",
340            true,
341            vec![quote! { inset.left }],
342            "Sets the left value of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
343        ),
344        (
345            "right",
346            true,
347            vec![quote! { inset.right }],
348            "Sets the right value of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
349        ),
350        (
351            "gap",
352            false,
353            vec![quote! { gap.width }, quote! { gap.height }],
354            "Sets the gap between rows and columns in flex layouts. [Docs](https://tailwindcss.com/docs/gap)"
355        ),
356        (
357            "gap_x",
358            false,
359            vec![quote! { gap.width }],
360            "Sets the gap between columns in flex layouts. [Docs](https://tailwindcss.com/docs/gap#changing-row-and-column-gaps-independently)"
361        ),
362        (
363            "gap_y",
364            false,
365            vec![quote! { gap.height }],
366            "Sets the gap between rows in flex layouts. [Docs](https://tailwindcss.com/docs/gap#changing-row-and-column-gaps-independently)"
367        ),
368    ]
369}
370
371/// Returns a vec of (Suffix size, tokens that correspond to this size, documentation)
372fn box_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
373    vec![
374        ("0", quote! { px(0.) }, "0px"),
375        ("0p5", quote! { rems(0.125) }, "2px (0.125rem)"),
376        ("1", quote! { rems(0.25) }, "4px (0.25rem)"),
377        ("1p5", quote! { rems(0.375) }, "6px (0.375rem)"),
378        ("2", quote! { rems(0.5) }, "8px (0.5rem)"),
379        ("2p5", quote! { rems(0.625) }, "10px (0.625rem)"),
380        ("3", quote! { rems(0.75) }, "12px (0.75rem)"),
381        ("3p5", quote! { rems(0.875) }, "14px (0.875rem)"),
382        ("4", quote! { rems(1.) }, "16px (1rem)"),
383        ("5", quote! { rems(1.25) }, "20px (1.25rem)"),
384        ("6", quote! { rems(1.5) }, "24px (1.5rem)"),
385        ("7", quote! { rems(1.75) }, "28px (1.75rem)"),
386        ("8", quote! { rems(2.0) }, "32px (2rem)"),
387        ("9", quote! { rems(2.25) }, "36px (2.25rem)"),
388        ("10", quote! { rems(2.5) }, "40px (2.5rem)"),
389        ("11", quote! { rems(2.75) }, "44px (2.75rem)"),
390        ("12", quote! { rems(3.) }, "48px (3rem)"),
391        ("16", quote! { rems(4.) }, "64px (4rem)"),
392        ("20", quote! { rems(5.) }, "80px (5rem)"),
393        ("24", quote! { rems(6.) }, "96px (6rem)"),
394        ("32", quote! { rems(8.) }, "128px (8rem)"),
395        ("40", quote! { rems(10.) }, "160px (10rem)"),
396        ("48", quote! { rems(12.) }, "192px (12rem)"),
397        ("56", quote! { rems(14.) }, "224px (14rem)"),
398        ("64", quote! { rems(16.) }, "256px (16rem)"),
399        ("72", quote! { rems(18.) }, "288px (18rem)"),
400        ("80", quote! { rems(20.) }, "320px (20rem)"),
401        ("96", quote! { rems(24.) }, "384px (24rem)"),
402        ("112", quote! { rems(28.) }, "448px (28rem)"),
403        ("128", quote! { rems(32.) }, "512px (32rem)"),
404        ("auto", quote! { auto() }, "Auto"),
405        ("px", quote! { px(1.) }, "1px"),
406        ("full", quote! { relative(1.) }, "100%"),
407        ("1_2", quote! { relative(0.5) }, "50% (1/2)"),
408        ("1_3", quote! { relative(1./3.) }, "33% (1/3)"),
409        ("2_3", quote! { relative(2./3.) }, "66% (2/3)"),
410        ("1_4", quote! { relative(0.25) }, "25% (1/4)"),
411        ("2_4", quote! { relative(0.5) }, "50% (2/4)"),
412        ("3_4", quote! { relative(0.75) }, "75% (3/4)"),
413        ("1_5", quote! { relative(0.2) }, "20% (1/5)"),
414        ("2_5", quote! { relative(0.4) }, "40% (2/5)"),
415        ("3_5", quote! { relative(0.6) }, "60% (3/5)"),
416        ("4_5", quote! { relative(0.8) }, "80% (4/5)"),
417        ("1_6", quote! { relative(1./6.) }, "16% (1/6)"),
418        ("5_6", quote! { relative(5./6.) }, "80% (5/6)"),
419        ("1_12", quote! { relative(1./12.) }, "8% (1/12)"),
420    ]
421}
422
423fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>, &'static str)> {
424    vec![
425        (
426            "rounded",
427            vec![
428                quote! { corner_radii.top_left },
429                quote! { corner_radii.top_right },
430                quote! { corner_radii.bottom_right },
431                quote! { corner_radii.bottom_left },
432            ],
433            "Sets the border radius of the element. [Docs](https://tailwindcss.com/docs/border-radius)"
434        ),
435        (
436            "rounded_t",
437            vec![
438                quote! { corner_radii.top_left },
439                quote! { corner_radii.top_right },
440            ],
441            "Sets the border radius of the top side of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-sides-separately)"
442        ),
443        (
444            "rounded_b",
445            vec![
446                quote! { corner_radii.bottom_left },
447                quote! { corner_radii.bottom_right },
448            ],
449            "Sets the border radius of the bottom side of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-sides-separately)"
450        ),
451        (
452            "rounded_r",
453            vec![
454                quote! { corner_radii.top_right },
455                quote! { corner_radii.bottom_right },
456            ],
457            "Sets the border radius of the right side of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-sides-separately)"
458        ),
459        (
460            "rounded_l",
461            vec![
462                quote! { corner_radii.top_left },
463                quote! { corner_radii.bottom_left },
464            ],
465            "Sets the border radius of the left side of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-sides-separately)"
466        ),
467        (
468            "rounded_tl",
469            vec![quote! { corner_radii.top_left }],
470            "Sets the border radius of the top left corner of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-corners-separately)"
471        ),
472        (
473            "rounded_tr",
474            vec![quote! { corner_radii.top_right }],
475            "Sets the border radius of the top right corner of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-corners-separately)"
476        ),
477        (
478            "rounded_bl",
479            vec![quote! { corner_radii.bottom_left }],
480            "Sets the border radius of the bottom left corner of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-corners-separately)"
481        ),
482        (
483            "rounded_br",
484            vec![quote! { corner_radii.bottom_right }],
485            "Sets the border radius of the bottom right corner of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-corners-separately)"
486        ),
487    ]
488}
489
490fn corner_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
491    vec![
492        ("none", quote! { px(0.) }, "0px"),
493        ("sm", quote! { rems(0.125) }, "2px (0.125rem)"),
494        ("md", quote! { rems(0.25) }, "4px (0.25rem)"),
495        ("lg", quote! { rems(0.5) }, "8px (0.5rem)"),
496        ("xl", quote! { rems(0.75) }, "12px (0.75rem)"),
497        ("2xl", quote! { rems(1.) }, "16px (1rem)"),
498        ("3xl", quote! { rems(1.5) }, "24px (1.5rem)"),
499        ("full", quote! {  px(9999.) }, "9999px"),
500    ]
501}
502
503fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>, &'static str)> {
504    vec![
505        (
506            "border",
507            vec![
508                quote! { border_widths.top },
509                quote! { border_widths.right },
510                quote! { border_widths.bottom },
511                quote! { border_widths.left },
512            ],
513            "Sets the border width of the element. [Docs](https://tailwindcss.com/docs/border-width)"
514        ),
515        (
516            "border_t",
517            vec![quote! { border_widths.top }],
518            "Sets the border width of the top side of the element. [Docs](https://tailwindcss.com/docs/border-width#individual-sides)"
519        ),
520        (
521            "border_b",
522            vec![quote! { border_widths.bottom }],
523            "Sets the border width of the bottom side of the element. [Docs](https://tailwindcss.com/docs/border-width#individual-sides)"
524        ),
525        (
526            "border_r",
527            vec![quote! { border_widths.right }],
528            "Sets the border width of the right side of the element. [Docs](https://tailwindcss.com/docs/border-width#individual-sides)"
529        ),
530        (
531            "border_l",
532            vec![quote! { border_widths.left }],
533            "Sets the border width of the left side of the element. [Docs](https://tailwindcss.com/docs/border-width#individual-sides)"
534        ),
535        (
536            "border_x",
537            vec![
538                quote! { border_widths.left },
539                quote! { border_widths.right },
540            ],
541            "Sets the border width of the vertical sides of the element. [Docs](https://tailwindcss.com/docs/border-width#horizontal-and-vertical-sides)"
542        ),
543        (
544            "border_y",
545            vec![
546                quote! { border_widths.top },
547                quote! { border_widths.bottom },
548            ],
549            "Sets the border width of the horizontal sides of the element. [Docs](https://tailwindcss.com/docs/border-width#horizontal-and-vertical-sides)"
550        ),
551    ]
552}
553
554fn border_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
555    vec![
556        ("", quote! { px(1.)}, "1px"),
557        ("0", quote! { px(0.)}, "0px"),
558        ("1", quote! { px(1.) }, "1px"),
559        ("2", quote! { px(2.) }, "2px"),
560        ("3", quote! { px(3.) }, "3px"),
561        ("4", quote! { px(4.) }, "4px"),
562        ("5", quote! { px(5.) }, "5px"),
563        ("6", quote! { px(6.) }, "6px"),
564        ("7", quote! { px(7.) }, "7px"),
565        ("8", quote! { px(8.) }, "8px"),
566        ("9", quote! { px(9.) }, "9px"),
567        ("10", quote! { px(10.) }, "10px"),
568        ("11", quote! { px(11.) }, "11px"),
569        ("12", quote! { px(12.) }, "12px"),
570        ("16", quote! { px(16.) }, "16px"),
571        ("20", quote! { px(20.) }, "20px"),
572        ("24", quote! { px(24.) }, "24px"),
573        ("32", quote! { px(32.) }, "32px"),
574    ]
575}