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