use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph, Widget},
};
use crate::domain::slash_commands::SlashCommand;
use crate::render::theme::Theme;
const MAX_VISIBLE_ROWS: usize = 8;
pub struct SlashPaletteWidget<'a> {
pub theme: &'a Theme,
pub commands: Vec<&'static SlashCommand>,
pub selected_index: usize,
}
impl<'a> Widget for SlashPaletteWidget<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let total = self.commands.len();
let scroll_offset = if self.selected_index >= MAX_VISIBLE_ROWS {
self.selected_index + 1 - MAX_VISIBLE_ROWS
} else {
0
};
let visible_end = (scroll_offset + MAX_VISIBLE_ROWS).min(total);
let title = if total > MAX_VISIBLE_ROWS {
format!(
" Commands ({}-{} of {}) ↑↓ navigate · Tab complete · Esc dismiss ",
scroll_offset + 1,
visible_end,
total
)
} else {
format!(
" Commands ({}) ↑↓ navigate · Tab complete · Esc dismiss ",
total
)
};
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::new().fg(self.theme.colors.border.to_color()))
.title(title);
if self.commands.is_empty() {
let line = Line::from(vec![Span::styled(
" No matching commands",
Style::new().fg(self.theme.colors.text_disabled.to_color()),
)]);
Paragraph::new(vec![line]).block(block).render(area, buf);
return;
}
let mut lines: Vec<Line> = Vec::with_capacity(MAX_VISIBLE_ROWS);
for (offset, cmd) in self.commands[scroll_offset..visible_end].iter().enumerate() {
let absolute_index = scroll_offset + offset;
let is_selected = absolute_index == self.selected_index;
let mut name_part = format!("/{}", cmd.name);
if let Some(hint) = cmd.arg_hint {
name_part.push(' ');
name_part.push_str(hint);
}
let name_style = if is_selected {
Style::new()
.fg(self.theme.colors.text_highlight.to_color())
.add_modifier(Modifier::BOLD | Modifier::REVERSED)
} else {
Style::new()
.fg(self.theme.colors.info.to_color())
.add_modifier(Modifier::BOLD)
};
let desc_style = if is_selected {
Style::new()
.fg(self.theme.colors.text_primary.to_color())
.add_modifier(Modifier::REVERSED)
} else {
Style::new().fg(self.theme.colors.text_secondary.to_color())
};
let padded_name = format!(" {:<22}", name_part);
lines.push(Line::from(vec![
Span::styled(padded_name, name_style),
Span::styled(format!(" {}", cmd.description), desc_style),
]));
}
Paragraph::new(lines).block(block).render(area, buf);
}
}