mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:23:33 +00:00
feat: improve input field functionality with double-Esc clear and exact history
- Add double-Esc to clear input field (press Esc twice within 500ms) - Fix command history to store exact input including slash commands - Maintain input focus when new chat content is added above - Prevent cursor from disappearing during content updates 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
This commit is contained in:
@@ -60,6 +60,7 @@ pub(crate) struct ChatComposer {
|
||||
token_usage_info: Option<TokenUsageInfo>,
|
||||
has_focus: bool,
|
||||
has_chat_history: bool,
|
||||
last_esc_time: Option<std::time::Instant>,
|
||||
}
|
||||
|
||||
/// Popup state – at most one can be visible at any time.
|
||||
@@ -91,6 +92,7 @@ impl ChatComposer {
|
||||
token_usage_info: None,
|
||||
has_focus: has_input_focus,
|
||||
has_chat_history: false,
|
||||
last_esc_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,8 +300,11 @@ impl ChatComposer {
|
||||
// Get the full command text before clearing
|
||||
let command_text = self.textarea.text().to_string();
|
||||
|
||||
// Record the exact slash command that was typed
|
||||
self.history.record_local_submission(&command_text);
|
||||
|
||||
// Send command to the app layer with full text.
|
||||
self.app_event_tx.send(AppEvent::DispatchCommand(*cmd, command_text));
|
||||
self.app_event_tx.send(AppEvent::DispatchCommand(*cmd, command_text.clone()));
|
||||
|
||||
// Clear textarea so no residual text remains.
|
||||
self.textarea.set_text("");
|
||||
@@ -510,6 +515,31 @@ impl ChatComposer {
|
||||
/// Handle key event when no popup is visible.
|
||||
fn handle_key_event_without_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool) {
|
||||
match key_event {
|
||||
// -------------------------------------------------------------
|
||||
// Handle Esc key for double-press clearing
|
||||
// -------------------------------------------------------------
|
||||
KeyEvent {
|
||||
code: KeyCode::Esc, ..
|
||||
} => {
|
||||
// Check if this is a double-Esc press
|
||||
const DOUBLE_ESC_THRESHOLD: std::time::Duration = std::time::Duration::from_millis(500);
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
if let Some(last_time) = self.last_esc_time {
|
||||
if now.duration_since(last_time) < DOUBLE_ESC_THRESHOLD && !self.textarea.is_empty() {
|
||||
// Double-Esc detected, clear the input field
|
||||
self.textarea.set_text("");
|
||||
self.pending_pastes.clear();
|
||||
self.last_esc_time = None;
|
||||
self.history.reset_navigation();
|
||||
return (InputResult::None, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Single Esc - just record the time
|
||||
self.last_esc_time = Some(now);
|
||||
(InputResult::None, false)
|
||||
}
|
||||
// -------------------------------------------------------------
|
||||
// History navigation (Up / Down) – only when the composer is not
|
||||
// empty or when the cursor is at the correct position, to avoid
|
||||
@@ -541,6 +571,9 @@ impl ChatComposer {
|
||||
modifiers: KeyModifiers::NONE,
|
||||
..
|
||||
} => {
|
||||
// Record the exact text that was typed (before replacement)
|
||||
let original_text = self.textarea.text().to_string();
|
||||
|
||||
let mut text = self.textarea.text().to_string();
|
||||
self.textarea.set_text("");
|
||||
|
||||
@@ -555,7 +588,7 @@ impl ChatComposer {
|
||||
if text.is_empty() {
|
||||
(InputResult::None, true)
|
||||
} else {
|
||||
self.history.record_local_submission(&text);
|
||||
self.history.record_local_submission(&original_text);
|
||||
(InputResult::Submitted(text), true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,12 @@ impl ChatComposerHistory {
|
||||
self.last_history_text = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset navigation state (used when clearing input with double-Esc)
|
||||
pub fn reset_navigation(&mut self) {
|
||||
self.history_cursor = None;
|
||||
self.last_history_text = None;
|
||||
}
|
||||
|
||||
/// Should Up/Down key presses be interpreted as history navigation given
|
||||
/// the current content and cursor position of `textarea`?
|
||||
|
||||
@@ -391,6 +391,21 @@ impl BottomPane<'_> {
|
||||
self.live_ring = None;
|
||||
}
|
||||
|
||||
/// Ensure input focus is maintained, especially after redraws or content updates
|
||||
pub(crate) fn ensure_input_focus(&mut self) {
|
||||
// Only ensure focus if there's no active modal view
|
||||
if self.active_view.is_none() {
|
||||
self.has_input_focus = true;
|
||||
// Reset any transient state that might affect focus
|
||||
// Clear any temporary status overlays that might interfere
|
||||
if !self.is_task_running {
|
||||
self.live_status = None;
|
||||
}
|
||||
// Ensure composer knows it has focus
|
||||
self.composer.set_ctrl_c_quit_hint(self.ctrl_c_quit_hint, self.has_input_focus);
|
||||
}
|
||||
}
|
||||
|
||||
// Removed restart_live_status_with_text – no longer used by the current streaming UI.
|
||||
}
|
||||
|
||||
|
||||
@@ -509,6 +509,8 @@ impl ChatWidget<'_> {
|
||||
matches!(cell, HistoryCell::UserPrompt { .. })
|
||||
});
|
||||
self.bottom_pane.set_has_chat_history(has_conversation);
|
||||
// Ensure input focus is maintained when new content arrives
|
||||
self.bottom_pane.ensure_input_focus();
|
||||
// Request redraw to show new history
|
||||
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||
}
|
||||
@@ -1765,6 +1767,8 @@ impl ChatWidget<'_> {
|
||||
for line in lines {
|
||||
self.history_cells.push(HistoryCell::new_text_line(line));
|
||||
}
|
||||
// Ensure input focus is maintained when adding streamed content
|
||||
self.bottom_pane.ensure_input_focus();
|
||||
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||
}
|
||||
|
||||
@@ -1812,6 +1816,8 @@ impl ChatWidget<'_> {
|
||||
for line in lines {
|
||||
self.history_cells.push(HistoryCell::new_text_line(line));
|
||||
}
|
||||
// Ensure input focus is maintained when adding streamed content
|
||||
self.bottom_pane.ensure_input_focus();
|
||||
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user