Skip to main content

max / alloy_tui

Apply rustfmt across the crate Formatting only, no behavior change. cargo check --all-targets passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-21 23:46 UTC
Commit: 1f49989536f74800defbafe2b9e83947b899004c
Parent: 4b077f9
8 files changed, +188 insertions, -56 deletions
@@ -25,7 +25,11 @@ pub struct AlloyConnector<'a> {
25 25
26 26 impl<'a> AlloyConnector<'a> {
27 27 pub fn new(theme: &'a Theme, from_y: u16, to_y: u16) -> Self {
28 - Self { theme, from_y, to_y }
28 + Self {
29 + theme,
30 + from_y,
31 + to_y,
32 + }
29 33 }
30 34 }
31 35
@@ -129,11 +133,7 @@ mod tests {
129 133 AlloyConnector::new(&theme, from_y, to_y).render(area, &mut buf);
130 134
131 135 (0..height)
132 - .map(|y| {
133 - (0..width)
134 - .map(|x| buf[(x, y)].symbol())
135 - .collect::<String>()
136 - })
136 + .map(|y| (0..width).map(|x| buf[(x, y)].symbol()).collect::<String>())
137 137 .collect()
138 138 }
139 139
M src/cursor.rs +20 -4
@@ -32,7 +32,11 @@ impl Cursor {
32 32 /// from reporting row 0 as selected, which renders as a selection marker
33 33 /// on a row that does not exist.
34 34 pub const fn selected(&self) -> Option<usize> {
35 - if self.len == 0 { None } else { Some(self.index) }
35 + if self.len == 0 {
36 + None
37 + } else {
38 + Some(self.index)
39 + }
36 40 }
37 41
38 42 pub const fn len(&self) -> usize {
@@ -91,7 +95,11 @@ mod tests {
91 95 assert_eq!(cursor.selected(), None);
92 96 cursor.next();
93 97 cursor.prev();
94 - assert_eq!(cursor.selected(), None, "movement on an empty list is inert");
98 + assert_eq!(
99 + cursor.selected(),
100 + None,
101 + "movement on an empty list is inert"
102 + );
95 103 }
96 104
97 105 #[test]
@@ -99,9 +107,17 @@ mod tests {
99 107 let mut cursor = Cursor::new();
100 108 cursor.resize(3);
101 109 cursor.prev();
102 - assert_eq!(cursor.selected(), Some(0), "no wrap to the end from the top");
110 + assert_eq!(
111 + cursor.selected(),
112 + Some(0),
113 + "no wrap to the end from the top"
114 + );
103 115 cursor.move_by(99);
104 - assert_eq!(cursor.selected(), Some(2), "no wrap to the top from the end");
116 + assert_eq!(
117 + cursor.selected(),
118 + Some(2),
119 + "no wrap to the top from the end"
120 + );
105 121 }
106 122
107 123 // The case this type exists for: a refresh returns fewer rows than the
M src/focus.rs +19 -4
@@ -70,9 +70,17 @@ mod tests {
70 70 fn wraps_in_both_directions() {
71 71 let mut ring = FocusRing::new(3);
72 72 ring.prev();
73 - assert_eq!(ring.current(), 2, "prev from the first slot wraps to the last");
73 + assert_eq!(
74 + ring.current(),
75 + 2,
76 + "prev from the first slot wraps to the last"
77 + );
74 78 ring.next();
75 - assert_eq!(ring.current(), 0, "next from the last slot wraps to the first");
79 + assert_eq!(
80 + ring.current(),
81 + 0,
82 + "next from the last slot wraps to the first"
83 + );
76 84 }
77 85
78 86 // An empty ring is what a view has before its panes load. The movers must be
@@ -83,7 +91,10 @@ mod tests {
83 91 ring.next();
84 92 ring.prev();
85 93 assert_eq!(ring.current(), 0);
86 - assert!(!ring.is_focused(0), "nothing is focused when there are no slots");
94 + assert!(
95 + !ring.is_focused(0),
96 + "nothing is focused when there are no slots"
97 + );
87 98 }
88 99
89 100 #[test]
@@ -98,6 +109,10 @@ mod tests {
98 109 fn focus_ignores_out_of_range_slots() {
99 110 let mut ring = FocusRing::new(2);
100 111 ring.focus(5);
101 - assert_eq!(ring.current(), 0, "an out-of-range focus leaves the ring where it was");
112 + assert_eq!(
113 + ring.current(),
114 + 0,
115 + "an out-of-range focus leaves the ring where it was"
116 + );
102 117 }
103 118 }
M src/keys.rs +56 -14
@@ -85,8 +85,14 @@ mod tests {
85 85
86 86 #[test]
87 87 fn ctrl_s_saves_but_bare_s_does_not() {
88 - assert_eq!(classify(key(KeyCode::Char('s'), KeyModifiers::CONTROL)), Action::Save);
89 - assert_eq!(classify(key(KeyCode::Char('s'), KeyModifiers::NONE)), Action::Passthrough);
88 + assert_eq!(
89 + classify(key(KeyCode::Char('s'), KeyModifiers::CONTROL)),
90 + Action::Save
91 + );
92 + assert_eq!(
93 + classify(key(KeyCode::Char('s'), KeyModifiers::NONE)),
94 + Action::Passthrough
95 + );
90 96 }
91 97
92 98 // Terminals disagree on how they report Shift-Tab: some send BackTab with no
@@ -95,22 +101,46 @@ mod tests {
95 101 // in the stack.
96 102 #[test]
97 103 fn both_shift_tab_encodings_move_focus_backward() {
98 - assert_eq!(classify(key(KeyCode::BackTab, KeyModifiers::NONE)), Action::PrevFocus);
99 - assert_eq!(classify(key(KeyCode::BackTab, KeyModifiers::SHIFT)), Action::PrevFocus);
100 - assert_eq!(classify(key(KeyCode::Tab, KeyModifiers::SHIFT)), Action::PrevFocus);
101 - assert_eq!(classify(key(KeyCode::Tab, KeyModifiers::NONE)), Action::NextFocus);
104 + assert_eq!(
105 + classify(key(KeyCode::BackTab, KeyModifiers::NONE)),
106 + Action::PrevFocus
107 + );
108 + assert_eq!(
109 + classify(key(KeyCode::BackTab, KeyModifiers::SHIFT)),
110 + Action::PrevFocus
111 + );
112 + assert_eq!(
113 + classify(key(KeyCode::Tab, KeyModifiers::SHIFT)),
114 + Action::PrevFocus
115 + );
116 + assert_eq!(
117 + classify(key(KeyCode::Tab, KeyModifiers::NONE)),
118 + Action::NextFocus
119 + );
102 120 }
103 121
104 122 #[test]
105 123 fn unreserved_keys_pass_through() {
106 - assert_eq!(classify(key(KeyCode::Char('j'), KeyModifiers::NONE)), Action::Passthrough);
107 - assert_eq!(classify(key(KeyCode::Down, KeyModifiers::NONE)), Action::Passthrough);
124 + assert_eq!(
125 + classify(key(KeyCode::Char('j'), KeyModifiers::NONE)),
126 + Action::Passthrough
127 + );
128 + assert_eq!(
129 + classify(key(KeyCode::Down, KeyModifiers::NONE)),
130 + Action::Passthrough
131 + );
108 132 }
109 133
110 134 #[test]
111 135 fn h_and_l_move_between_tabs() {
112 - assert_eq!(classify(key(KeyCode::Char('l'), KeyModifiers::NONE)), Action::NextTab);
113 - assert_eq!(classify(key(KeyCode::Char('h'), KeyModifiers::NONE)), Action::PrevTab);
136 + assert_eq!(
137 + classify(key(KeyCode::Char('l'), KeyModifiers::NONE)),
138 + Action::NextTab
139 + );
140 + assert_eq!(
141 + classify(key(KeyCode::Char('h'), KeyModifiers::NONE)),
142 + Action::PrevTab
143 + );
114 144 }
115 145
116 146 // Tabs and focus are two navigation axes and must stay on separate keys.
@@ -118,15 +148,27 @@ mod tests {
118 148 // view, so a tab bar claiming Tab would silently redefine it everywhere.
119 149 #[test]
120 150 fn tab_key_still_means_focus_not_tabs() {
121 - assert_eq!(classify(key(KeyCode::Tab, KeyModifiers::NONE)), Action::NextFocus);
122 - assert_eq!(classify(key(KeyCode::BackTab, KeyModifiers::NONE)), Action::PrevFocus);
151 + assert_eq!(
152 + classify(key(KeyCode::Tab, KeyModifiers::NONE)),
153 + Action::NextFocus
154 + );
155 + assert_eq!(
156 + classify(key(KeyCode::BackTab, KeyModifiers::NONE)),
157 + Action::PrevFocus
158 + );
123 159 }
124 160
125 161 // j/k stay unreserved so a list cursor keeps them. Only the horizontal half
126 162 // of the vim pair is spoken for.
127 163 #[test]
128 164 fn vertical_vim_keys_are_not_claimed_by_tabs() {
129 - assert_eq!(classify(key(KeyCode::Char('j'), KeyModifiers::NONE)), Action::Passthrough);
130 - assert_eq!(classify(key(KeyCode::Char('k'), KeyModifiers::NONE)), Action::Passthrough);
165 + assert_eq!(
166 + classify(key(KeyCode::Char('j'), KeyModifiers::NONE)),
167 + Action::Passthrough
168 + );
169 + assert_eq!(
170 + classify(key(KeyCode::Char('k'), KeyModifiers::NONE)),
171 + Action::Passthrough
172 + );
131 173 }
132 174 }
M src/layout.rs +12 -3
@@ -110,7 +110,10 @@ pub fn panes(area: Rect) -> PaneAreas {
110 110 let left_width = usable - usable / 2;
111 111
112 112 PaneAreas {
113 - left: Rect { width: left_width, ..area },
113 + left: Rect {
114 + width: left_width,
115 + ..area
116 + },
114 117 gutter: Rect {
115 118 x: area.x + left_width,
116 119 width: GUTTER_WIDTH,
@@ -149,7 +152,10 @@ mod tests {
149 152 let areas = panes(Rect::new(0, 0, 81, 20));
150 153 assert_eq!(areas.left.width, 39);
151 154 assert_eq!(areas.right.width, 39);
152 - assert_eq!(areas.left.width + areas.gutter.width + areas.right.width, 81);
155 + assert_eq!(
156 + areas.left.width + areas.gutter.width + areas.right.width,
157 + 81
158 + );
153 159 }
154 160
155 161 // A narrow terminal collapses to one pane rather than two unusable slivers.
@@ -179,7 +185,10 @@ mod tests {
179 185 #[test]
180 186 fn short_terminal_drops_the_log_pane() {
181 187 let areas = console(Rect::new(0, 0, 80, 10));
182 - assert_eq!(areas.log.height, 0, "log is dropped below the minimum height");
188 + assert_eq!(
189 + areas.log.height, 0,
190 + "log is dropped below the minimum height"
191 + );
183 192 assert_eq!(areas.body.height, 9);
184 193 assert_eq!(areas.footer.height, 1);
185 194 }
M src/text.rs +3 -1
@@ -30,7 +30,9 @@ pub fn secondary(theme: &Theme, s: impl Into<String>) -> Span<'static> {
30 30 pub fn bold(theme: &Theme, s: impl Into<String>) -> Span<'static> {
31 31 Span::styled(
32 32 s.into(),
33 - Style::default().fg(theme.content_primary).add_modifier(Modifier::BOLD),
33 + Style::default()
34 + .fg(theme.content_primary)
35 + .add_modifier(Modifier::BOLD),
34 36 )
35 37 }
36 38
M src/theme.rs +30 -10
@@ -15,8 +15,8 @@
15 15 //! and pull colors from it. Apps build one `Theme` per theme load (via
16 16 //! `makeover::load_theme` + `Theme::from_theme`) and thread it through.
17 17
18 - use ratatui::style::Color;
19 18 use makeover::{Rgb, ThemeColors};
19 + use ratatui::style::Color;
20 20
21 21 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
22 22 pub enum Mode {
@@ -178,11 +178,17 @@ fn indexed(c: Color) -> Color {
178 178 /// As [`indexed`], but guaranteed to stay legible against `on`.
179 179 fn indexed_against(c: Color, on: Color) -> Color {
180 180 match (c, on) {
181 - (Color::Rgb(r, g, b), Color::Rgb(br, bg, bb)) => Color::Indexed(makeover::quantize_against(
182 - Rgb { r, g, b },
183 - Rgb { r: br, g: bg, b: bb },
184 - &makeover::ANSI_16,
185 - ) as u8),
181 + (Color::Rgb(r, g, b), Color::Rgb(br, bg, bb)) => {
182 + Color::Indexed(makeover::quantize_against(
183 + Rgb { r, g, b },
184 + Rgb {
185 + r: br,
186 + g: bg,
187 + b: bb,
188 + },
189 + &makeover::ANSI_16,
190 + ) as u8)
191 + }
186 192 _ => indexed(c),
187 193 }
188 194 }
@@ -253,7 +259,11 @@ fn mix_linear_srgb(a: Rgb, b: Rgb, t: f32) -> Rgb {
253 259 }
254 260
255 261 fn srgb_to_linear(c: Rgb) -> (f32, f32, f32) {
256 - (channel_to_linear(c.r), channel_to_linear(c.g), channel_to_linear(c.b))
262 + (
263 + channel_to_linear(c.r),
264 + channel_to_linear(c.g),
265 + channel_to_linear(c.b),
266 + )
257 267 }
258 268
259 269 fn linear_to_srgb(c: (f32, f32, f32)) -> Rgb {
@@ -266,11 +276,19 @@ fn linear_to_srgb(c: (f32, f32, f32)) -> Rgb {
266 276
267 277 fn channel_to_linear(c: u8) -> f32 {
268 278 let c = c as f32 / 255.0;
269 - if c <= 0.04045 { c / 12.92 } else { ((c + 0.055) / 1.055).powf(2.4) }
279 + if c <= 0.04045 {
280 + c / 12.92
281 + } else {
282 + ((c + 0.055) / 1.055).powf(2.4)
283 + }
270 284 }
271 285
272 286 fn channel_to_srgb(c: f32) -> u8 {
273 - let v = if c <= 0.0031308 { c * 12.92 } else { 1.055 * c.powf(1.0 / 2.4) - 0.055 };
287 + let v = if c <= 0.0031308 {
288 + c * 12.92
289 + } else {
290 + 1.055 * c.powf(1.0 / 2.4) - 0.055
291 + };
274 292 (v * 255.0).round().clamp(0.0, 255.0) as u8
275 293 }
276 294
@@ -291,7 +309,9 @@ mod tests {
291 309 (got.r, got.g, got.b),
292 310 (0x7f, 0x78, 0x6d),
293 311 "border-strong derivation drifted; got #{:02x}{:02x}{:02x}, expected #7f786d",
294 - got.r, got.g, got.b
312 + got.r,
313 + got.g,
314 + got.b
295 315 );
296 316 }
297 317
M src/widgets.rs +42 -14
@@ -33,7 +33,10 @@ pub struct AlloyBlock<'a> {
33 33
34 34 impl<'a> AlloyBlock<'a> {
35 35 pub fn new(theme: &'a Theme) -> Self {
36 - Self { theme, focused: false }
36 + Self {
37 + theme,
38 + focused: false,
39 + }
37 40 }
38 41
39 42 pub fn focused(mut self, focused: bool) -> Self {
@@ -428,19 +431,23 @@ impl Widget for AlloyModal<'_> {
428 431 )))
429 432 .style(base)
430 433 .wrap(ratatui::widgets::Wrap { trim: true })
431 - .render(Rect { height: message_height, ..inner }, buf);
432 - }
433 -
434 - Paragraph::new(keys)
435 - .style(base)
436 434 .render(
437 435 Rect {
438 - y: inner.y + inner.height - 1,
439 - height: 1,
436 + height: message_height,
440 437 ..inner
441 438 },
442 439 buf,
443 440 );
441 + }
442 +
443 + Paragraph::new(keys).style(base).render(
444 + Rect {
445 + y: inner.y + inner.height - 1,
446 + height: 1,
447 + ..inner
448 + },
449 + buf,
450 + );
444 451 }
445 452 }
446 453
@@ -593,9 +600,21 @@ mod tests {
593 600 #[test]
594 601 fn row_y_is_none_for_rows_scrolled_out_of_view() {
595 602 let area = Rect::new(0, 0, 20, 10);
596 - assert_eq!(list_row_y(area, 50, Some(25), 0), None, "above the viewport");
597 - assert_eq!(list_row_y(area, 50, Some(25), 49), None, "below the viewport");
598 - assert_eq!(list_row_y(area, 3, Some(0), 9), None, "past the end of the list");
603 + assert_eq!(
604 + list_row_y(area, 50, Some(25), 0),
605 + None,
606 + "above the viewport"
607 + );
608 + assert_eq!(
609 + list_row_y(area, 50, Some(25), 49),
610 + None,
611 + "below the viewport"
612 + );
613 + assert_eq!(
614 + list_row_y(area, 3, Some(0), 9),
615 + None,
616 + "past the end of the list"
617 + );
599 618 }
600 619
601 620 fn render_tabs(selected: usize, width: u16) -> String {
@@ -610,7 +629,10 @@ mod tests {
610 629 #[test]
611 630 fn selected_tab_is_bracketed_and_others_are_not() {
612 631 let rendered = render_tabs(0, 60);
613 - assert!(rendered.contains("[ installed ]"), "selected tab is bracketed");
632 + assert!(
633 + rendered.contains("[ installed ]"),
634 + "selected tab is bracketed"
635 + );
614 636 assert!(!rendered.contains("[ boxes ]"), "unselected tabs are not");
615 637 assert!(rendered.contains("boxes"), "unselected labels still render");
616 638 }
@@ -710,7 +732,13 @@ mod tests {
710 732 .iter()
711 733 .map(|cell| cell.symbol())
712 734 .collect::<String>();
713 - assert!(rendered.contains("nmcli run 9"), "newest entry must be visible");
714 - assert!(!rendered.contains("nmcli run 0"), "oldest entry must have scrolled off");
735 + assert!(
736 + rendered.contains("nmcli run 9"),
737 + "newest entry must be visible"
738 + );
739 + assert!(
740 + !rendered.contains("nmcli run 0"),
741 + "oldest entry must have scrolled off"
742 + );
715 743 }
716 744 }