1use crate::core::geometry::Edges;
4use crate::core::render::{Renderable, Rendered};
5use crate::core::style::Style;
6use crate::core::text::{Line, Span, Text};
7use crate::core::theme::theme;
8use crate::core::width::{visible_width, wrap};
9use crate::output::compose::pad;
10
11struct Pair {
13 key: String,
14 value: Text,
15}
16
17pub struct KeyValue {
31 pairs: Vec<Pair>,
32 separator: String,
33 key_width: Option<u16>,
34 key_style: Style,
35 value_style: Style,
36 item_gap: u16,
37 wrap_values: bool,
38 margin: Edges,
39}
40
41impl Default for KeyValue {
42 fn default() -> Self {
43 Self {
44 pairs: Vec::new(),
45 separator: " ".to_string(),
46 key_width: None,
47 key_style: Style::new().bold(),
48 value_style: theme().secondary,
49 item_gap: 0,
50 wrap_values: false,
51 margin: Edges::default(),
52 }
53 }
54}
55
56impl KeyValue {
57 pub fn new() -> Self {
59 Self::default()
60 }
61
62 #[must_use]
64 pub fn add(
65 mut self,
66 key: impl Into<String>,
67 value: impl Into<Text>,
68 ) -> Self {
69 self.pairs.push(Pair {
70 key: key.into(),
71 value: value.into(),
72 });
73 self
74 }
75
76 #[must_use]
78 pub fn separator(mut self, separator: impl Into<String>) -> Self {
79 self.separator = separator.into();
80 self
81 }
82
83 #[must_use]
85 pub fn key_width(mut self, width: u16) -> Self {
86 self.key_width = Some(width);
87 self
88 }
89
90 #[must_use]
92 pub fn key_style(mut self, style: Style) -> Self {
93 self.key_style = style;
94 self
95 }
96
97 #[must_use]
99 pub fn value_style(mut self, style: Style) -> Self {
100 self.value_style = style;
101 self
102 }
103
104 #[must_use]
106 pub fn item_gap(mut self, gap: u16) -> Self {
107 self.item_gap = gap;
108 self
109 }
110
111 #[must_use]
113 pub fn wrap_values(mut self, wrap: bool) -> Self {
114 self.wrap_values = wrap;
115 self
116 }
117
118 #[must_use]
120 pub fn margin(mut self, margin: Edges) -> Self {
121 self.margin = margin;
122 self
123 }
124
125 fn resolved_key_width(&self) -> usize {
127 match self.key_width {
128 Some(width) => width as usize,
129 None => self
130 .pairs
131 .iter()
132 .map(|pair| visible_width(&pair.key))
133 .max()
134 .unwrap_or(0),
135 }
136 }
137}
138
139impl Renderable for KeyValue {
140 fn render(&self, max_width: u16) -> Rendered {
141 let key_width = self.resolved_key_width();
142 let prefix_width = key_width + visible_width(&self.separator);
143 let value_width =
144 (max_width as usize).saturating_sub(prefix_width).max(1);
145 let mut lines = Vec::new();
146 for (index, pair) in self.pairs.iter().enumerate() {
147 if index > 0 {
148 push_gap(&mut lines, self.item_gap);
149 }
150 self.push_pair(&mut lines, pair, key_width, value_width);
151 }
152 pad(&Rendered::new(lines), self.margin)
153 }
154}
155
156impl KeyValue {
157 fn push_pair(
159 &self,
160 lines: &mut Vec<Line>,
161 pair: &Pair,
162 key_width: usize,
163 value_width: usize,
164 ) {
165 let value_lines = self.value_lines(pair, value_width);
166 for (row, value_line) in value_lines.into_iter().enumerate() {
167 let key_cell = if row == 0 {
168 pair.key.clone()
169 } else {
170 String::new()
171 };
172 lines.push(self.compose_line(&key_cell, key_width, value_line));
173 }
174 }
175
176 fn value_lines(&self, pair: &Pair, value_width: usize) -> Vec<Line> {
178 if !self.wrap_values {
179 return pair.value.lines.clone();
180 }
181 let mut out = Vec::new();
182 for line in &pair.value.lines {
183 for chunk in wrap(&line.plain(), value_width) {
184 out.push(Line::styled(chunk, self.value_style));
185 }
186 }
187 out
188 }
189
190 fn compose_line(
192 &self,
193 key: &str,
194 key_width: usize,
195 value_line: Line,
196 ) -> Line {
197 let key_pad = key_width.saturating_sub(visible_width(key));
198 let mut spans = vec![
199 Span::styled(key.to_string(), self.key_style),
200 Span::raw(" ".repeat(key_pad)),
201 Span::raw(self.separator.clone()),
202 ];
203 for mut span in value_line.spans {
204 span.style = self.value_style.patch(span.style);
205 spans.push(span);
206 }
207 Line::new(spans)
208 }
209}
210
211fn push_gap(lines: &mut Vec<Line>, count: u16) {
213 for _ in 0..count {
214 lines.push(Line::default());
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 fn plain(rendered: &Rendered) -> Vec<String> {
223 rendered.lines.iter().map(Line::plain).collect()
224 }
225
226 #[test]
227 fn aligns_keys_to_the_widest() {
228 let kv = KeyValue::new().add("a", "1").add("name", "2");
229 let lines = plain(&kv.render(40));
230 assert_eq!(lines[0], "a 1");
231 assert_eq!(lines[1], "name 2");
232 }
233
234 #[test]
235 fn wraps_long_values_when_enabled() {
236 let kv = KeyValue::new()
237 .wrap_values(true)
238 .add("k", "one two three four");
239 let lines = plain(&kv.render(10));
240 assert!(lines.len() > 1);
241 }
242}