Skip to content

Commit d767842

Browse files
authored
Merge pull request #96 from syncable-dev/develop
feat: refactored display
2 parents b6dd0fa + b44fa1d commit d767842

9 files changed

Lines changed: 2069 additions & 1684 deletions

File tree

src/analyzer/display.rs

Lines changed: 0 additions & 1684 deletions
This file was deleted.

src/analyzer/display/box_drawer.rs

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
//! Box drawing utilities for creating formatted text boxes in the terminal
2+
3+
use colored::*;
4+
use crate::analyzer::display::utils::{visual_width, truncate_to_width};
5+
6+
/// Content line for measuring and drawing
7+
#[derive(Debug, Clone)]
8+
struct ContentLine {
9+
label: String,
10+
value: String,
11+
label_colored: bool,
12+
}
13+
14+
impl ContentLine {
15+
fn new(label: &str, value: &str, label_colored: bool) -> Self {
16+
Self {
17+
label: label.to_string(),
18+
value: value.to_string(),
19+
label_colored,
20+
}
21+
}
22+
23+
fn separator() -> Self {
24+
Self {
25+
label: "SEPARATOR".to_string(),
26+
value: String::new(),
27+
label_colored: false,
28+
}
29+
}
30+
}
31+
32+
/// Box drawer that pre-calculates optimal dimensions
33+
pub struct BoxDrawer {
34+
title: String,
35+
lines: Vec<ContentLine>,
36+
min_width: usize,
37+
max_width: usize,
38+
}
39+
40+
impl BoxDrawer {
41+
pub fn new(title: &str) -> Self {
42+
Self {
43+
title: title.to_string(),
44+
lines: Vec::new(),
45+
min_width: 60,
46+
max_width: 120, // Reduced from 150 for better terminal compatibility
47+
}
48+
}
49+
50+
pub fn add_line(&mut self, label: &str, value: &str, label_colored: bool) {
51+
self.lines.push(ContentLine::new(label, value, label_colored));
52+
}
53+
54+
pub fn add_value_only(&mut self, value: &str) {
55+
self.lines.push(ContentLine::new("", value, false));
56+
}
57+
58+
pub fn add_separator(&mut self) {
59+
self.lines.push(ContentLine::separator());
60+
}
61+
62+
/// Calculate optimal box width based on content
63+
fn calculate_optimal_width(&self) -> usize {
64+
let title_width = visual_width(&self.title) + 6; // "┌─ " + title + " " + extra padding
65+
let mut max_content_width = 0;
66+
67+
// Calculate the actual rendered width for each line
68+
for line in &self.lines {
69+
if line.label == "SEPARATOR" {
70+
continue;
71+
}
72+
73+
let rendered_width = self.calculate_rendered_line_width(line);
74+
max_content_width = max_content_width.max(rendered_width);
75+
}
76+
77+
// Add reasonable buffer for content
78+
let content_width_with_buffer = max_content_width + 4; // More buffer for safety
79+
80+
// Box needs padding: "│ " + content + " │" = content + 4
81+
let needed_width = content_width_with_buffer + 4;
82+
83+
// Use the maximum of title width and content width
84+
let optimal_width = title_width.max(needed_width).max(self.min_width);
85+
optimal_width.min(self.max_width)
86+
}
87+
88+
/// Calculate the actual rendered width of a line as it will appear
89+
fn calculate_rendered_line_width(&self, line: &ContentLine) -> usize {
90+
let label_width = visual_width(&line.label);
91+
let value_width = visual_width(&line.value);
92+
93+
if !line.label.is_empty() && !line.value.is_empty() {
94+
// Label + value: need space between them
95+
// For colored labels, ensure minimum spacing
96+
let min_label_space = if line.label_colored { 25 } else { label_width };
97+
min_label_space + 2 + value_width // 2 spaces minimum between label and value
98+
} else if !line.value.is_empty() {
99+
// Value only
100+
value_width
101+
} else if !line.label.is_empty() {
102+
// Label only
103+
label_width
104+
} else {
105+
// Empty line
106+
0
107+
}
108+
}
109+
110+
/// Draw the complete box
111+
pub fn draw(&self) -> String {
112+
let box_width = self.calculate_optimal_width();
113+
let content_width = box_width - 4; // Available space for content
114+
115+
let mut output = Vec::new();
116+
117+
// Top border
118+
output.push(self.draw_top(box_width));
119+
120+
// Content lines
121+
for line in &self.lines {
122+
if line.label == "SEPARATOR" {
123+
output.push(self.draw_separator(box_width));
124+
} else if line.label.is_empty() && line.value.is_empty() {
125+
output.push(self.draw_empty_line(box_width));
126+
} else {
127+
output.push(self.draw_content_line(line, content_width));
128+
}
129+
}
130+
131+
// Bottom border
132+
output.push(self.draw_bottom(box_width));
133+
134+
output.join("\n")
135+
}
136+
137+
fn draw_top(&self, width: usize) -> String {
138+
let title_colored = self.title.bright_cyan();
139+
let title_len = visual_width(&self.title);
140+
141+
// "┌─ " + title + " " + remaining dashes + "┐"
142+
let prefix_len = 3; // "┌─ "
143+
let suffix_len = 1; // "┐"
144+
let title_space = 1; // space after title
145+
146+
let remaining_space = width - prefix_len - title_len - title_space - suffix_len;
147+
148+
format!("┌─ {} {}┐",
149+
title_colored,
150+
"─".repeat(remaining_space)
151+
)
152+
}
153+
154+
fn draw_bottom(&self, width: usize) -> String {
155+
format!("└{}┘", "─".repeat(width - 2))
156+
}
157+
158+
fn draw_separator(&self, width: usize) -> String {
159+
format!("│ {} │", "─".repeat(width - 4).dimmed())
160+
}
161+
162+
fn draw_empty_line(&self, width: usize) -> String {
163+
format!("│ {} │", " ".repeat(width - 4))
164+
}
165+
166+
fn draw_content_line(&self, line: &ContentLine, content_width: usize) -> String {
167+
// Format the label with color if needed
168+
let formatted_label = if line.label_colored && !line.label.is_empty() {
169+
line.label.bright_white().to_string()
170+
} else {
171+
line.label.clone()
172+
};
173+
174+
// Calculate actual display widths (use original label for width)
175+
let label_display_width = visual_width(&line.label);
176+
let value_display_width = visual_width(&line.value);
177+
178+
// Build the content
179+
let content = if !line.label.is_empty() && !line.value.is_empty() {
180+
// Both label and value - ensure proper spacing
181+
let min_label_space = if line.label_colored { 25 } else { label_display_width };
182+
let label_padding = min_label_space.saturating_sub(label_display_width);
183+
let remaining_space = content_width.saturating_sub(min_label_space + 2); // 2 for spacing
184+
185+
if value_display_width <= remaining_space {
186+
// Value fits - right align it
187+
let value_padding = remaining_space.saturating_sub(value_display_width);
188+
format!("{}{:<width$} {}{}",
189+
formatted_label,
190+
"",
191+
" ".repeat(value_padding),
192+
line.value,
193+
width = label_padding
194+
)
195+
} else {
196+
// Value too long - truncate it
197+
let truncated_value = truncate_to_width(&line.value, remaining_space.saturating_sub(3));
198+
format!("{}{:<width$} {}",
199+
formatted_label,
200+
"",
201+
truncated_value,
202+
width = label_padding
203+
)
204+
}
205+
} else if !line.value.is_empty() {
206+
// Value only - left align
207+
if value_display_width <= content_width {
208+
format!("{:<width$}", line.value, width = content_width)
209+
} else {
210+
truncate_to_width(&line.value, content_width)
211+
}
212+
} else if !line.label.is_empty() {
213+
// Label only - left align
214+
if label_display_width <= content_width {
215+
format!("{:<width$}", formatted_label, width = content_width)
216+
} else {
217+
truncate_to_width(&formatted_label, content_width)
218+
}
219+
} else {
220+
// Empty line
221+
" ".repeat(content_width)
222+
};
223+
224+
// Ensure final content is exactly the right width
225+
let actual_width = visual_width(&content);
226+
let final_content = if actual_width < content_width {
227+
format!("{}{}", content, " ".repeat(content_width - actual_width))
228+
} else if actual_width > content_width {
229+
truncate_to_width(&content, content_width)
230+
} else {
231+
content
232+
};
233+
234+
format!("│ {} │", final_content)
235+
}
236+
}

0 commit comments

Comments
 (0)