1use crate::{Widget, draw_text_span};
17use ftui_core::geometry::Rect;
18use ftui_render::frame::Frame;
19use ftui_style::Style;
20use std::fmt::Debug;
21
22pub struct Pretty<'a, T: Debug + ?Sized> {
27 value: &'a T,
28 compact: bool,
29 style: Style,
30}
31
32impl<'a, T: Debug + ?Sized> Pretty<'a, T> {
33 #[must_use]
35 pub fn new(value: &'a T) -> Self {
36 Self {
37 value,
38 compact: false,
39 style: Style::default(),
40 }
41 }
42
43 #[must_use]
45 pub fn with_compact(mut self, compact: bool) -> Self {
46 self.compact = compact;
47 self
48 }
49
50 #[must_use]
52 pub fn with_style(mut self, style: Style) -> Self {
53 self.style = style;
54 self
55 }
56
57 #[must_use]
59 pub fn formatted_text(&self) -> String {
60 if self.compact {
61 format!("{:?}", self.value)
62 } else {
63 format!("{:#?}", self.value)
64 }
65 }
66}
67
68impl<T: Debug + ?Sized> Widget for Pretty<'_, T> {
69 fn render(&self, area: Rect, frame: &mut Frame) {
70 if area.width == 0 || area.height == 0 {
71 return;
72 }
73
74 let text = self.formatted_text();
75 let max_x = area.right();
76
77 for (row_idx, line) in text.lines().enumerate() {
78 if row_idx >= area.height as usize {
79 break;
80 }
81 let y = area.y.saturating_add(row_idx as u16);
82 draw_text_span(frame, area.x, y, line, self.style, max_x);
83 }
84 }
85
86 fn is_essential(&self) -> bool {
87 false
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use ftui_render::frame::Frame;
95 use ftui_render::grapheme_pool::GraphemePool;
96
97 #[test]
98 fn format_simple_value() {
99 let widget = Pretty::new(&42i32);
100 assert_eq!(widget.formatted_text(), "42");
101 }
102
103 #[test]
104 fn format_vec() {
105 let data = vec![1, 2, 3];
106 let widget = Pretty::new(&data);
107 let text = widget.formatted_text();
108 assert!(text.contains("1"));
109 assert!(text.contains("2"));
110 assert!(text.contains("3"));
111 }
112
113 #[test]
114 fn format_compact() {
115 let data = vec![1, 2, 3];
116 let compact = Pretty::new(&data).with_compact(true);
117 let text = compact.formatted_text();
118 assert_eq!(text.lines().count(), 1);
120 }
121
122 #[test]
123 fn format_pretty() {
124 let data = vec![1, 2, 3];
125 let pretty = Pretty::new(&data).with_compact(false);
126 let text = pretty.formatted_text();
127 assert!(text.lines().count() > 1);
129 }
130
131 #[derive(Debug)]
132 #[allow(dead_code)]
133 struct TestStruct {
134 name: String,
135 value: i32,
136 }
137
138 #[test]
139 fn format_struct() {
140 let s = TestStruct {
141 name: "hello".to_string(),
142 value: 42,
143 };
144 let widget = Pretty::new(&s);
145 let text = widget.formatted_text();
146 assert!(text.contains("name"));
147 assert!(text.contains("hello"));
148 assert!(text.contains("42"));
149 }
150
151 #[test]
152 fn format_string() {
153 let widget = Pretty::new("hello world");
154 let text = widget.formatted_text();
155 assert!(text.contains("hello world"));
156 }
157
158 #[test]
159 fn render_basic() {
160 let data = vec![1, 2, 3];
161 let widget = Pretty::new(&data);
162
163 let mut pool = GraphemePool::new();
164 let mut frame = Frame::new(40, 10, &mut pool);
165 let area = Rect::new(0, 0, 40, 10);
166 widget.render(area, &mut frame);
167
168 let cell = frame.buffer.get(0, 0).unwrap();
170 assert_eq!(cell.content.as_char(), Some('['));
171 }
172
173 #[test]
174 fn render_zero_area() {
175 let widget = Pretty::new(&42);
176 let mut pool = GraphemePool::new();
177 let mut frame = Frame::new(40, 10, &mut pool);
178 widget.render(Rect::new(0, 0, 0, 0), &mut frame); }
180
181 #[test]
182 fn render_truncated_height() {
183 let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
184 let widget = Pretty::new(&data);
185 let mut pool = GraphemePool::new();
186 let mut frame = Frame::new(40, 3, &mut pool);
187 let area = Rect::new(0, 0, 40, 3);
188 widget.render(area, &mut frame); }
190
191 #[test]
192 fn is_not_essential() {
193 let widget = Pretty::new(&42);
194 assert!(!widget.is_essential());
195 }
196
197 #[test]
198 fn format_empty_vec() {
199 let data: Vec<i32> = vec![];
200 let widget = Pretty::new(&data);
201 assert_eq!(widget.formatted_text(), "[]");
202 }
203
204 #[test]
205 fn format_nested() {
206 let data = vec![vec![1, 2], vec![3, 4]];
207 let widget = Pretty::new(&data);
208 let text = widget.formatted_text();
209 assert!(text.lines().count() > 1);
210 }
211
212 #[test]
213 fn format_option() {
214 let some: Option<i32> = Some(42);
215 let none: Option<i32> = None;
216 assert!(Pretty::new(&some).formatted_text().contains("42"));
217 assert!(Pretty::new(&none).formatted_text().contains("None"));
218 }
219}