use std::io::stderr;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::Rect;
use ratatui::{Terminal, TerminalOptions, Viewport};
use crossterm::terminal;
use crate::error::GwmError;
use crate::ui::widgets::NoticeWidget;
const MIN_ERROR_WIDTH: u16 = 40;
const MAX_ERROR_WIDTH: u16 = 100;
const DEFAULT_ERROR_WIDTH: u16 = 60;
fn get_error_display_width() -> u16 {
terminal::size()
.map(|(w, _)| {
let available = w.saturating_sub(4);
available.clamp(MIN_ERROR_WIDTH, MAX_ERROR_WIDTH)
})
.unwrap_or(DEFAULT_ERROR_WIDTH)
}
fn calculate_error_height(error: &GwmError) -> u16 {
let mut height: u16 = 6;
let details = error.details();
if details.path.is_some()
|| details.branch.is_some()
|| !details.files.is_empty()
|| !details.extra.is_empty()
{
height += 1;
if details.path.is_some() {
height += 1;
}
if details.branch.is_some() {
height += 1;
}
height += details.extra.len().min(10) as u16;
if !details.files.is_empty() {
height += 2; height += details.files.len().min(5) as u16;
if details.files.len() > 5 {
height += 1; }
}
}
let suggestions = error.suggestions();
if !suggestions.is_empty() {
height += 2; for s in &suggestions {
height += 1; if s.command.is_some() {
height += 1; }
}
}
height
}
pub fn print_structured_error(error: &GwmError) {
let title = error.title();
let messages = vec![error.to_string()];
let error_details = error.details();
let mut details = Vec::new();
if let Some(ref path) = error_details.path {
details.push(("Path".to_string(), path.display().to_string()));
}
if let Some(ref branch) = error_details.branch {
details.push(("Branch".to_string(), branch.clone()));
}
for (key, value) in &error_details.extra {
details.push((key.clone(), value.clone()));
}
if !error_details.files.is_empty() {
details.push(("Modified files".to_string(), String::new()));
for file in error_details.files.iter().take(5) {
details.push((String::new(), file.clone()));
}
if error_details.files.len() > 5 {
details.push((
String::new(),
format!("... and {} more", error_details.files.len() - 5),
));
}
}
let suggestions = error.suggestions();
let widget = NoticeWidget::error(title, &messages)
.with_details(details)
.with_suggestions(suggestions);
let height = calculate_error_height(error);
let backend = CrosstermBackend::new(stderr());
let options = TerminalOptions {
viewport: Viewport::Inline(height),
};
match Terminal::with_options(backend, options) {
Ok(mut terminal) => {
let _ = terminal.draw(|frame| {
let area = frame.area();
let max_width = get_error_display_width().min(area.width);
let limited_area = Rect::new(area.x, area.y, max_width, area.height);
frame.render_widget(widget, limited_area);
});
eprintln!();
}
Err(_) => {
eprintln!("\x1b[31m✗ {}: {}\x1b[0m", title, error);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_print_structured_error_not_git_repository() {
let error = GwmError::NotGitRepository;
print_structured_error(&error);
}
#[test]
fn test_print_structured_error_branch_exists() {
let error = GwmError::BranchExists("feature/test".to_string());
print_structured_error(&error);
}
#[test]
fn test_print_structured_error_uncommitted_changes() {
let error = GwmError::UncommittedChanges {
path: PathBuf::from("/path/to/worktree"),
};
print_structured_error(&error);
}
#[test]
fn test_print_structured_error_config() {
let error = GwmError::Config("invalid syntax".to_string());
print_structured_error(&error);
}
}