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}