steer_tui/tui/widgets/input_panel/
mode_title.rs1use ratatui::style::{Modifier, Style};
4use ratatui::text::{Line, Span};
5
6use crate::tui::InputMode;
7use crate::tui::get_spinner_char;
8use crate::tui::theme::{Component, Theme};
9
10#[derive(Debug)]
12pub struct ModeTitleWidget<'a> {
13 mode: InputMode,
14 is_processing: bool,
15 spinner_state: usize,
16 theme: &'a Theme,
17 has_content: bool,
18}
19
20impl<'a> ModeTitleWidget<'a> {
21 pub fn new(
23 mode: InputMode,
24 is_processing: bool,
25 spinner_state: usize,
26 theme: &'a Theme,
27 has_content: bool,
28 ) -> Self {
29 Self {
30 mode,
31 is_processing,
32 spinner_state,
33 theme,
34 has_content,
35 }
36 }
37
38 pub fn render(&self) -> Line<'static> {
40 let mut spans = vec![];
41
42 if self.is_processing {
44 spans.push(Span::styled(
45 format!(" {}", get_spinner_char(self.spinner_state)),
46 self.theme.style(Component::ToolCall),
47 ));
48 }
49
50 spans.push(Span::raw(" "));
52
53 let formatted_mode = self.get_formatted_mode();
54 if let Some(mode) = formatted_mode {
55 spans.push(mode);
56 spans.push(Span::styled(" │ ", self.theme.style(Component::DimText)));
57 }
58
59 let keybinds = self.get_mode_keybinds();
61 spans.extend(format_keybinds(&keybinds, self.theme));
62
63 spans.push(Span::raw(" "));
64 Line::from(spans)
65 }
66
67 fn get_formatted_mode(&self) -> Option<Span<'static>> {
69 let mode_name = match self.mode {
70 InputMode::Simple => return None,
71 InputMode::VimNormal => "NORMAL",
72 InputMode::VimInsert => "INSERT",
73 InputMode::BashCommand => "Bash",
74 InputMode::AwaitingApproval => "Awaiting Approval",
75 InputMode::ConfirmExit => "Confirm Exit",
76 InputMode::EditMessageSelection => "Edit Selection",
77 InputMode::FuzzyFinder => "Search",
78 InputMode::Setup => "Setup",
79 };
80
81 let component = match self.mode {
82 InputMode::ConfirmExit => Component::ErrorBold,
83 InputMode::BashCommand => Component::CommandPrompt,
84 InputMode::AwaitingApproval => Component::ErrorBold,
85 InputMode::EditMessageSelection => Component::SelectionHighlight,
86 InputMode::FuzzyFinder => Component::SelectionHighlight,
87 _ => Component::ModelInfo,
88 };
89
90 Some(Span::styled(mode_name, self.theme.style(component)))
91 }
92
93 fn get_mode_keybinds(&self) -> Vec<(&'static str, &'static str)> {
95 match self.mode {
96 InputMode::Simple => {
97 if self.has_content {
98 vec![("Enter", "send"), ("ESC ESC", "clear")]
99 } else {
100 vec![
101 ("Enter", "send"),
102 ("ESC ESC", "edit previous"),
103 ("!", "bash"),
104 ("/", "command"),
105 ("@", "file"),
106 ]
107 }
108 }
109 InputMode::VimNormal => {
110 if self.has_content {
111 vec![("i", "insert"), ("ESC ESC", "clear"), ("hjkl", "move")]
112 } else {
113 vec![
114 ("i", "insert"),
115 ("ESC ESC", "edit previous"),
116 ("!", "bash"),
117 ("/", "command"),
118 ]
119 }
120 }
121 InputMode::VimInsert => {
122 vec![("Esc", "normal"), ("ESC ESC", "clear"), ("Enter", "send")]
123 }
124 InputMode::BashCommand => {
125 vec![("Enter", "execute"), ("Esc", "cancel")]
126 }
127 InputMode::AwaitingApproval => {
128 vec![]
130 }
131 InputMode::ConfirmExit => {
132 vec![("y/Y", "confirm"), ("any other key", "cancel")]
133 }
134 InputMode::EditMessageSelection => {
135 vec![("↑↓", "navigate"), ("Enter", "select"), ("Esc", "cancel")]
136 }
137 InputMode::FuzzyFinder => {
138 vec![("↑↓", "navigate"), ("Enter", "select"), ("Esc", "cancel")]
139 }
140 InputMode::Setup => {
141 vec![]
143 }
144 }
145 }
146}
147
148fn format_keybind(key: &str, description: &str, theme: &Theme) -> Vec<Span<'static>> {
150 vec![
151 Span::styled(
152 format!("[{key}]"),
153 Style::default().add_modifier(Modifier::BOLD),
154 ),
155 Span::styled(format!(" {description}"), theme.style(Component::DimText)),
156 ]
157}
158
159fn format_keybinds(keybinds: &[(&str, &str)], theme: &Theme) -> Vec<Span<'static>> {
161 let mut spans = Vec::new();
162 for (i, (key, description)) in keybinds.iter().enumerate() {
163 spans.extend(format_keybind(key, description, theme));
164 if i < keybinds.len() - 1 {
165 spans.push(Span::styled(" │ ", theme.style(Component::DimText)));
166 }
167 }
168 spans
169}