返回 CodeWhale
lists.rs
1 //! Gallery entries for package Lists: the list in its states and the empty
2 //! state. The fuzzy picker's entries live in `picker.rs`, beside the other
3 //! pickers.
4
5 use ratatui::{buffer::Buffer, layout::Rect};
6
7 use super::Entry;
8 use crate::{
9 EmptyMark, EmptyState, List, ListRow, ListRowState, ListState, Paint, Role, Theme, text,
10 };
11
12 /// A settings-style row: a name, its value, and optionally a second line.
13 /// It shows how a caller paints its own row: `ListRowState::ink` for the
14 /// name, `Muted` for the rest, a reason in place of the value when disabled.
15 struct Setting {
16 name: &'static str,
17 value: &'static str,
18 note: Option<&'static str>,
19 disabled: Option<&'static str>,
20 }
21
22 impl Setting {
23 const fn new(name: &'static str, value: &'static str) -> Self {
24 Self {
25 name,
26 value,
27 note: None,
28 disabled: None,
29 }
30 }
31
32 const fn note(mut self, note: &'static str) -> Self {
33 self.note = Some(note);
34 self
35 }
36
37 const fn disabled(mut self, reason: &'static str) -> Self {
38 self.disabled = Some(reason);
39 self
40 }
41 }
42
43 impl ListRow for Setting {
44 fn height(&self, _width: u16) -> u16 {
45 1 + u16::from(self.note.is_some())
46 }
47
48 fn is_disabled(&self) -> bool {
49 self.disabled.is_some()
50 }
51
52 fn paint_row(&self, area: Rect, buf: &mut Buffer, theme: &Theme, state: ListRowState) {
53 let ascii = theme.ascii();
54 let width = usize::from(area.width);
55 let name_w = (width / 2).min(20);
56 let name = text::display_safe(self.name);
57 let name = text::truncate(&name, name_w, ascii);
58 buf.set_stringn(area.x, area.y, &name, name_w, state.ink(theme));
59 let value = self.disabled.unwrap_or(self.value);
60 let value_w = width.saturating_sub(name_w + 2);
61 if value_w >= 4 {
62 let value = text::display_safe(value);
63 let value = text::truncate(&value, value_w, ascii);
64 let x = area.x + u16::try_from(name_w + 2).unwrap_or(0);
65 buf.set_stringn(x, area.y, &value, value_w, theme.fg(Role::Muted));
66 }
67 if let Some(note) = self.note
68 && area.height > 1
69 {
70 let note = text::display_safe(note);
71 let note = text::truncate_words(&note, width, ascii);
72 buf.set_stringn(area.x, area.y + 1, &note, width, theme.fg(Role::Muted));
73 }
74 }
75 }
76
77 fn settings() -> Vec<Setting> {
78 vec![
79 Setting::new("Theme", "Shoreline, follows terminal"),
80 Setting::new("Motion", "Full"),
81 Setting::new("Density", "Comfortable"),
82 Setting::new("Provider", "Not connected").disabled("Sign in first"),
83 Setting::new("Language", "English"),
84 ]
85 }
86
87 fn list(area: Rect, buf: &mut Buffer, theme: &Theme) {
88 let rows = settings();
89 List::new(&rows, ListState::new(1)).paint(area, buf, theme);
90 }
91
92 /// Twelve rows in five: the scrollbar, and a selection held in view.
93 fn list_scrolling(area: Rect, buf: &mut Buffer, theme: &Theme) {
94 let rows: Vec<String> = (1..=12).map(|n| format!("Session {n:02}")).collect();
95 let mut state = ListState::new(8);
96 state.scroll_into_view(rows.len(), area.height, |_| 1);
97 List::new(&rows, state).paint(area, buf, theme);
98 }
99
100 /// Rows two lines tall; the selection takes both.
101 fn list_tall(area: Rect, buf: &mut Buffer, theme: &Theme) {
102 let rows = [
103 Setting::new("Approvals", "Ask").note("When the agent may run a command without you"),
104 Setting::new("Sandbox", "Workspace").note("Where files can be written"),
105 Setting::new("Network", "Off").note("Whether tools can reach the internet"),
106 Setting::new("Telemetry", "Off").note("What leaves this machine"),
107 ];
108 List::new(&rows, ListState::new(1)).paint(area, buf, theme);
109 }
110
111 /// Long names are cut with an ellipsis, between graphemes. (CJK text is
112 /// covered in `tests/lists.rs`: the gallery's own rule check holds the ASCII
113 /// profile to ASCII, which caller text in another script cannot meet.)
114 fn list_long(area: Rect, buf: &mut Buffer, theme: &Theme) {
115 let rows = [
116 "Refactor the session store so it keeps every receipt",
117 "src/components/receipt_table/cell_alignment.rs",
118 "Untitled",
119 "A name with trailing words that run past the edge",
120 ];
121 List::new(&rows, ListState::new(1)).paint(area, buf, theme);
122 }
123
124 fn empty_list(area: Rect, buf: &mut Buffer, theme: &Theme) {
125 let rows: [&str; 0] = [];
126 List::new(&rows, ListState::default())
127 .empty(
128 EmptyState::new("No saved sessions")
129 .action("Start one with a message")
130 .mark(EmptyMark::Glyph),
131 )
132 .paint(area, buf, theme);
133 }
134
135 fn empty_state(area: Rect, buf: &mut Buffer, theme: &Theme) {
136 EmptyState::new("No workflow runs yet")
137 .body("Runs appear here once a workflow starts, with what each one changed.")
138 .action("Start one with /workflow, or press n")
139 .paint(area, buf, theme);
140 }
141
142 fn empty_state_small(area: Rect, buf: &mut Buffer, theme: &Theme) {
143 EmptyState::new("Nothing queued")
144 .body("Messages you send while the agent works wait here.")
145 .action("Type a message")
146 .paint(area, buf, theme);
147 }
148
149 pub(crate) fn entries() -> Vec<Entry> {
150 vec![
151 Entry {
152 name: "list",
153 width: 64,
154 height: 5,
155 draw: list,
156 },
157 Entry {
158 name: "list-scrolling",
159 width: 64,
160 height: 5,
161 draw: list_scrolling,
162 },
163 Entry {
164 name: "list-tall-rows",
165 width: 64,
166 height: 6,
167 draw: list_tall,
168 },
169 Entry {
170 name: "list-long",
171 width: 40,
172 height: 4,
173 draw: list_long,
174 },
175 Entry {
176 name: "list-empty",
177 width: 48,
178 height: 5,
179 draw: empty_list,
180 },
181 Entry {
182 name: "empty-state",
183 width: 64,
184 height: 14,
185 draw: empty_state,
186 },
187 Entry {
188 name: "empty-state-small",
189 width: 40,
190 height: 6,
191 draw: empty_state_small,
192 },
193 ]
194 }
195
195 lines RUST