focus changing

This commit is contained in:
Daniel Edrisian
2025-08-31 12:47:03 -07:00
parent 3f8184034f
commit 2984f90dc4
5 changed files with 66 additions and 28 deletions

View File

@@ -124,6 +124,9 @@ impl App {
TuiEvent::Key(key_event) => {
self.handle_key_event(tui, key_event).await;
}
TuiEvent::FocusChanged(focused) => {
self.chat_widget.set_input_focus(focused);
}
TuiEvent::Paste(pasted) => {
// Many terminals convert newlines to \r when pasting (e.g., iTerm2),
// but tui-textarea expects \n. Normalize CR to LF.

View File

@@ -1236,30 +1236,35 @@ impl WidgetRef for ChatComposer {
ActivePopup::None => {
let bottom_line_rect = popup_rect;
let key_hint_style = Style::default().fg(Color::Cyan);
let mut hint = if self.ctrl_c_quit_hint {
vec![
Span::from(" "),
"Ctrl+C again".set_style(key_hint_style),
Span::from(" to quit"),
]
} else {
let newline_hint_key = if self.use_shift_enter_hint {
"Shift+⏎"
// Start with a visible focus state label.
let mut hint: Vec<Span> = vec![
Span::from(" "),
if self.has_focus { "Focused".green().into() } else { "Unfocused".magenta().into() },
Span::from(" "),
];
// Append the usual key hints.
hint.extend(
if self.ctrl_c_quit_hint {
vec![
"Ctrl+C again".set_style(key_hint_style).into(),
Span::from(" to quit"),
]
} else {
"Ctrl+J"
};
vec![
Span::from(" "),
"".set_style(key_hint_style),
Span::from(" send "),
newline_hint_key.set_style(key_hint_style),
Span::from(" newline "),
"Ctrl+T".set_style(key_hint_style),
Span::from(" transcript "),
"Ctrl+C".set_style(key_hint_style),
Span::from(" quit"),
]
};
let newline_hint_key = if self.use_shift_enter_hint { "Shift+⏎" } else { "Ctrl+J" };
vec![
"".set_style(key_hint_style).into(),
Span::from(" send "),
newline_hint_key.set_style(key_hint_style).into(),
Span::from(" newline "),
"Ctrl+T".set_style(key_hint_style).into(),
Span::from(" transcript "),
"Ctrl+C".set_style(key_hint_style).into(),
Span::from(" quit"),
]
}
);
if !self.ctrl_c_quit_hint && self.esc_backtrack_hint {
hint.push(Span::from(" "));

View File

@@ -99,6 +99,16 @@ impl BottomPane {
}
}
/// Update whether the bottom pane's composer has input focus.
pub(crate) fn set_has_input_focus(&mut self, has_focus: bool) {
self.has_input_focus = has_focus;
// Use existing API to propagate focus to the composer without changing the
// current Ctrl-C hint visibility.
self.composer
.set_ctrl_c_quit_hint(self.ctrl_c_quit_hint, self.has_input_focus);
self.request_redraw();
}
pub fn desired_height(&self, width: u16) -> u16 {
let top_margin = if self.active_view.is_some() { 0 } else { 1 };

View File

@@ -693,6 +693,12 @@ impl ChatWidget {
.map_or(0, |c| c.desired_height(width))
}
/// Update input focus state for the bottom pane/composer.
pub(crate) fn set_input_focus(&mut self, has_focus: bool) {
self.bottom_pane.set_has_input_focus(has_focus);
self.request_redraw();
}
pub(crate) fn handle_key_event(&mut self, key_event: KeyEvent) {
match key_event {
KeyEvent {

View File

@@ -18,7 +18,9 @@ use crossterm::SynchronizedUpdate;
use crossterm::cursor;
use crossterm::cursor::MoveTo;
use crossterm::event::DisableBracketedPaste;
use crossterm::event::DisableFocusChange;
use crossterm::event::EnableBracketedPaste;
use crossterm::event::EnableFocusChange;
use crossterm::event::Event;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
@@ -49,6 +51,8 @@ pub type Terminal = CustomTerminal<CrosstermBackend<Stdout>>;
pub fn set_modes() -> Result<()> {
execute!(stdout(), EnableBracketedPaste)?;
// Enable focus change reporting where supported; ignore errors on unsupported terminals.
let _ = execute!(stdout(), EnableFocusChange);
enable_raw_mode()?;
// Enable keyboard enhancement flags so modifiers for keys like Enter are disambiguated.
@@ -116,6 +120,8 @@ pub fn restore() -> Result<()> {
// Pop may fail on platforms that didn't support the push; ignore errors.
let _ = execute!(stdout(), PopKeyboardEnhancementFlags);
execute!(stdout(), DisableBracketedPaste)?;
// Disable focus change reporting if it was enabled; ignore errors.
let _ = execute!(stdout(), DisableFocusChange);
disable_raw_mode()?;
let _ = execute!(stdout(), crossterm::cursor::Show);
Ok(())
@@ -154,6 +160,8 @@ pub enum TuiEvent {
Key(KeyEvent),
Paste(String),
Draw,
/// Terminal focus changed: true when focused, false when unfocused.
FocusChanged(bool),
AttachImage {
path: PathBuf,
width: u32,
@@ -323,12 +331,12 @@ impl Tui {
}
Err(_) => {
// Fall back to normal key handling if no image is available.
yield TuiEvent::Key(key_event);
}
}
}
yield TuiEvent::Key(key_event);
}
}
}
crossterm::event::Event::Key(key_event) => {
crossterm::event::Event::Key(key_event) => {
#[cfg(unix)]
if matches!(
key_event,
@@ -363,6 +371,12 @@ impl Tui {
Event::Resize(_, _) => {
yield TuiEvent::Draw;
}
Event::FocusGained => {
yield TuiEvent::FocusChanged(true);
}
Event::FocusLost => {
yield TuiEvent::FocusChanged(false);
}
Event::Paste(pasted) => {
yield TuiEvent::Paste(pasted);
}