envelope_cli/tui/dialogs/
command_palette.rs1use ratatui::{
6 layout::Rect,
7 style::{Color, Modifier, Style},
8 text::{Line, Span},
9 widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph},
10 Frame,
11};
12
13use crate::tui::app::App;
14use crate::tui::commands::COMMANDS;
15use crate::tui::layout::centered_rect_fixed;
16
17pub fn render(frame: &mut Frame, app: &mut App) {
19 let width = 60;
20 let height = 20;
21 let area = centered_rect_fixed(width, height, frame.area());
22
23 frame.render_widget(Clear, area);
25
26 let block = Block::default()
27 .title(" Command Palette ")
28 .title_style(
29 Style::default()
30 .fg(Color::Cyan)
31 .add_modifier(Modifier::BOLD),
32 )
33 .borders(Borders::ALL)
34 .border_style(Style::default().fg(Color::Cyan));
35
36 frame.render_widget(block, area);
37
38 let input_area = Rect {
40 x: area.x + 1,
41 y: area.y + 1,
42 width: area.width - 2,
43 height: 1,
44 };
45
46 let input_line = Line::from(vec![
47 Span::styled("> ", Style::default().fg(Color::Cyan)),
48 Span::styled(app.command_input.clone(), Style::default().fg(Color::White)),
49 Span::styled("_", Style::default().fg(Color::Cyan)), ]);
51
52 frame.render_widget(Paragraph::new(input_line), input_area);
53
54 let results_area = Rect {
56 x: area.x + 1,
57 y: area.y + 3,
58 width: area.width - 2,
59 height: area.height - 4,
60 };
61
62 let filtered_commands: Vec<(usize, &crate::tui::commands::Command)> = COMMANDS
64 .iter()
65 .enumerate()
66 .filter(|(_, cmd)| {
67 if app.command_input.is_empty() {
68 true
69 } else {
70 let query = app.command_input.to_lowercase();
71 cmd.name.to_lowercase().contains(&query)
72 || cmd.description.to_lowercase().contains(&query)
73 }
74 })
75 .collect();
76
77 if filtered_commands.is_empty() {
78 let text = Paragraph::new("No matching commands").style(Style::default().fg(Color::Yellow));
79 frame.render_widget(text, results_area);
80 return;
81 }
82
83 let items: Vec<ListItem> = filtered_commands
85 .iter()
86 .map(|(_, cmd)| {
87 let line = Line::from(vec![
88 Span::styled(
89 format!("{:<20}", cmd.name),
90 Style::default().fg(Color::Cyan),
91 ),
92 Span::raw(" "),
93 Span::styled(cmd.description, Style::default().fg(Color::Yellow)),
94 ]);
95 ListItem::new(line)
96 })
97 .collect();
98
99 let list = List::new(items)
100 .highlight_style(
101 Style::default()
102 .bg(Color::DarkGray)
103 .add_modifier(Modifier::BOLD),
104 )
105 .highlight_symbol("▶ ");
106
107 let mut state = ListState::default();
108 let selected = app
109 .selected_command_index
110 .min(filtered_commands.len().saturating_sub(1));
111 state.select(Some(selected));
112
113 frame.render_stateful_widget(list, results_area, &mut state);
114}