fix(tui): show Ctrl+T exit hint in standard mode

Closes #206
This commit is contained in:
James Peter
2025-09-22 11:51:40 +10:00
parent 6716de57b7
commit 6243cf2737
4 changed files with 43 additions and 3 deletions
+1 -1
View File
@@ -941,7 +941,7 @@ impl App<'_> {
let _ = self.toggle_screen_mode(terminal);
// Propagate mode to widget so it can adapt layout
if let AppState::Chat { widget } = &mut self.app_state {
widget.standard_terminal_mode = !self.alt_screen_active;
widget.set_standard_terminal_mode(!self.alt_screen_active);
}
}
KeyEvent {
+20 -1
View File
@@ -119,6 +119,8 @@ pub(crate) struct ChatComposer {
using_chatgpt_auth: bool,
// Ephemeral footer notice and its expiry
footer_notice: Option<(String, std::time::Instant)>,
// Persistent hint for specific modes (e.g., standard terminal mode)
standard_terminal_hint: Option<String>,
// Persistent/ephemeral access-mode indicator shown on the left
access_mode_label: Option<String>,
access_mode_label_expiry: Option<std::time::Instant>,
@@ -174,6 +176,7 @@ impl ChatComposer {
animation_running: None,
using_chatgpt_auth,
footer_notice: None,
standard_terminal_hint: None,
access_mode_label: None,
access_mode_label_expiry: None,
access_mode_hint_expiry: None,
@@ -720,6 +723,10 @@ impl ChatComposer {
self.set_has_focus(has_focus);
}
pub fn set_standard_terminal_hint(&mut self, hint: Option<String>) {
self.standard_terminal_hint = hint;
}
pub(crate) fn insert_str(&mut self, text: &str) {
self.textarea.insert_str(text);
self.typed_anything = true; // Mark that user has interacted via programmatic insertion
@@ -1703,10 +1710,22 @@ impl WidgetRef for ChatComposer {
left_spans.push(Span::from(" again to quit").style(label_style));
}
if let Some(hint) = &self.standard_terminal_hint {
if left_spans.len() > 1 {
left_spans.push(Span::from(" "));
}
left_spans.push(
Span::from(hint.clone())
.style(Style::default().fg(crate::colors::warning()).add_modifier(Modifier::BOLD)),
);
}
// Append ephemeral footer notice if present and not expired
if let Some((msg, until)) = &self.footer_notice {
if std::time::Instant::now() <= *until {
if !self.ctrl_c_quit_hint { left_spans.push(Span::from(" ")); }
if left_spans.len() > 1 {
left_spans.push(Span::from(" "));
}
left_spans.push(Span::from(msg.clone()).style(Style::default().add_modifier(Modifier::DIM)));
}
}
+5
View File
@@ -376,6 +376,11 @@ impl BottomPane<'_> {
self.request_redraw();
}
pub(crate) fn set_standard_terminal_hint(&mut self, hint: Option<String>) {
self.composer.set_standard_terminal_hint(hint);
self.request_redraw();
}
pub(crate) fn show_ctrl_c_quit_hint(&mut self) {
self.ctrl_c_quit_hint = true;
self.composer
+17 -1
View File
@@ -2213,7 +2213,7 @@ impl ChatWidget<'_> {
// appears below it. Also insert the Popular commands immediately so users
// don't wait for MCP initialization to finish.
let mut w = new_widget;
w.standard_terminal_mode = !config.tui.alternate_screen;
w.set_standard_terminal_mode(!config.tui.alternate_screen);
if config.experimental_resume.is_none() {
w.history_push_top_next_req(history_cell::new_animated_welcome()); // tag: prelude
let connecting_mcp = !w.config.mcp_servers.is_empty();
@@ -2409,6 +2409,7 @@ impl ChatWidget<'_> {
synthetic_system_req: None,
system_cell_by_id: HashMap::new(),
};
w.set_standard_terminal_mode(!config.tui.alternate_screen);
// Welcome at top of first request for forked session too
w.history_push_top_next_req(history_cell::new_animated_welcome());
w
@@ -8536,6 +8537,21 @@ fn update_rate_limit_resets(
}
}
fn refresh_standard_terminal_hint(&mut self) {
if self.standard_terminal_mode {
let message = "Standard terminal mode active. Press Ctrl+T to return to full UI.";
self.bottom_pane
.set_standard_terminal_hint(Some(message.to_string()));
} else {
self.bottom_pane.set_standard_terminal_hint(None);
}
}
pub(crate) fn set_standard_terminal_mode(&mut self, enabled: bool) {
self.standard_terminal_mode = enabled;
self.refresh_standard_terminal_hint();
}
pub(crate) fn is_reasoning_shown(&self) -> bool {
// Check if any reasoning cell exists and if it's expanded
for cell in &self.history_cells {