use ratatui::{ buffer::Buffer, layout::Rect, style::{Color, Modifier, Style}, widgets::{Block, Borders, Clear, Widget}, }; /// A compact popup showing available key completions after a prefix key, /// Emacs which-key style. pub struct WhichKeyWidget<'a> { hints: &'a [(String, &'static str)], } impl<'a> WhichKeyWidget<'a> { pub fn new(hints: &'a [(String, &'static str)]) -> Self { Self { hints } } } impl Widget for WhichKeyWidget<'_> { fn render(self, area: Rect, buf: &mut Buffer) { if self.hints.is_empty() { return; } // Size: width fits the longest "key command" line, height = hint count + border let content_width = self .hints .iter() .map(|(k, cmd)| k.len() + 2 + cmd.len()) .max() .unwrap_or(10); let popup_w = (content_width as u16 + 4).min(area.width); // +4 for border + padding let popup_h = (self.hints.len() as u16 + 2).min(area.height); // +2 for border // Position: bottom-center, above the status bar let x = area.x + area.width.saturating_sub(popup_w) / 2; let y = area.y + area.height.saturating_sub(popup_h + 2); // 2 lines above bottom let popup_area = Rect::new(x, y, popup_w, popup_h); Clear.render(popup_area, buf); let block = Block::default() .borders(Borders::ALL) .border_style(Style::default().fg(Color::DarkGray)) .title(" which-key "); let inner = block.inner(popup_area); block.render(popup_area, buf); let key_style = Style::default() .fg(Color::Cyan) .add_modifier(Modifier::BOLD); let cmd_style = Style::default().fg(Color::Gray); for (i, (key_label, cmd_name)) in self.hints.iter().enumerate() { if i >= inner.height as usize { break; } let y = inner.y + i as u16; buf.set_string(inner.x + 1, y, key_label, key_style); let cmd_x = inner.x + 4; // fixed column for command names if cmd_x < inner.x + inner.width { buf.set_string(cmd_x, y, cmd_name, cmd_style); } } } }