input box fixes
This commit is contained in:
@@ -211,7 +211,6 @@ async fn run_interactive_retro(config: Config, show_prompt: bool, show_code: boo
|
|||||||
// Track multiline input
|
// Track multiline input
|
||||||
let mut multiline_buffer = String::new();
|
let mut multiline_buffer = String::new();
|
||||||
let mut in_multiline = false;
|
let mut in_multiline = false;
|
||||||
let mut input_buffer = String::new();
|
|
||||||
|
|
||||||
// Main event loop
|
// Main event loop
|
||||||
loop {
|
loop {
|
||||||
@@ -223,9 +222,6 @@ async fn run_interactive_retro(config: Config, show_prompt: bool, show_code: boo
|
|||||||
context.percentage_used(),
|
context.percentage_used(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update the displayed input buffer
|
|
||||||
tui.update_input(&input_buffer);
|
|
||||||
|
|
||||||
// Poll for keyboard events
|
// Poll for keyboard events
|
||||||
if event::poll(Duration::from_millis(50))? {
|
if event::poll(Duration::from_millis(50))? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
@@ -238,8 +234,48 @@ async fn run_interactive_retro(config: Config, show_prompt: bool, show_code: boo
|
|||||||
tui.exit();
|
tui.exit();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Emacs/bash-like shortcuts
|
||||||
|
KeyCode::Char('a') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
tui.cursor_home();
|
||||||
|
}
|
||||||
|
KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
tui.cursor_end();
|
||||||
|
}
|
||||||
|
KeyCode::Char('w') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
tui.delete_word();
|
||||||
|
}
|
||||||
|
KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
tui.delete_to_end();
|
||||||
|
}
|
||||||
|
KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
// Delete from beginning to cursor (similar to Ctrl-K but opposite direction)
|
||||||
|
let (input_buffer, cursor_pos) = tui.get_input_state();
|
||||||
|
if cursor_pos > 0 {
|
||||||
|
let after = input_buffer.chars().skip(cursor_pos).collect::<String>();
|
||||||
|
tui.update_input(&after);
|
||||||
|
tui.cursor_home();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Left => {
|
||||||
|
tui.cursor_left();
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
tui.cursor_right();
|
||||||
|
}
|
||||||
|
KeyCode::Home => {
|
||||||
|
tui.cursor_home();
|
||||||
|
}
|
||||||
|
KeyCode::End => {
|
||||||
|
tui.cursor_end();
|
||||||
|
}
|
||||||
|
KeyCode::Delete => {
|
||||||
|
tui.delete_char();
|
||||||
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
|
let (input_buffer, _) = tui.get_input_state();
|
||||||
if !input_buffer.is_empty() {
|
if !input_buffer.is_empty() {
|
||||||
|
// Clear the input for next command
|
||||||
|
tui.update_input("");
|
||||||
let trimmed = input_buffer.trim_end();
|
let trimmed = input_buffer.trim_end();
|
||||||
|
|
||||||
// Check if line ends with backslash for continuation
|
// Check if line ends with backslash for continuation
|
||||||
@@ -249,7 +285,6 @@ async fn run_interactive_retro(config: Config, show_prompt: bool, show_code: boo
|
|||||||
multiline_buffer.push_str(without_backslash);
|
multiline_buffer.push_str(without_backslash);
|
||||||
multiline_buffer.push('\n');
|
multiline_buffer.push('\n');
|
||||||
in_multiline = true;
|
in_multiline = true;
|
||||||
input_buffer.clear();
|
|
||||||
tui.status("MULTILINE INPUT");
|
tui.status("MULTILINE INPUT");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -266,8 +301,6 @@ async fn run_interactive_retro(config: Config, show_prompt: bool, show_code: boo
|
|||||||
input_buffer.clone()
|
input_buffer.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
input_buffer.clear();
|
|
||||||
|
|
||||||
let input = final_input.trim().to_string();
|
let input = final_input.trim().to_string();
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
@@ -305,10 +338,10 @@ async fn run_interactive_retro(config: Config, show_prompt: bool, show_code: boo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
input_buffer.push(c);
|
tui.insert_char(c);
|
||||||
}
|
}
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
input_buffer.pop();
|
tui.backspace();
|
||||||
}
|
}
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
tui.scroll_up();
|
tui.scroll_up();
|
||||||
@@ -322,11 +355,11 @@ async fn run_interactive_retro(config: Config, show_prompt: bool, show_code: boo
|
|||||||
KeyCode::PageDown => {
|
KeyCode::PageDown => {
|
||||||
tui.scroll_page_down();
|
tui.scroll_page_down();
|
||||||
}
|
}
|
||||||
KeyCode::Home => {
|
KeyCode::Home if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
tui.scroll_home();
|
tui.scroll_home(); // Ctrl+Home for scrolling to top
|
||||||
}
|
}
|
||||||
KeyCode::End => {
|
KeyCode::End if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
tui.scroll_end();
|
tui.scroll_end(); // Ctrl+End for scrolling to bottom
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ pub enum TuiMessage {
|
|||||||
struct TerminalState {
|
struct TerminalState {
|
||||||
/// Current input buffer
|
/// Current input buffer
|
||||||
input_buffer: String,
|
input_buffer: String,
|
||||||
|
/// Cursor position in input buffer (for editing)
|
||||||
|
cursor_position: usize,
|
||||||
/// Output history
|
/// Output history
|
||||||
output_history: Vec<String>,
|
output_history: Vec<String>,
|
||||||
/// Scroll position in output
|
/// Scroll position in output
|
||||||
@@ -115,6 +117,7 @@ impl TerminalState {
|
|||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
input_buffer: String::new(),
|
input_buffer: String::new(),
|
||||||
|
cursor_position: 0,
|
||||||
output_history: vec![
|
output_history: vec![
|
||||||
"WEYLAND-YUTANI SYSTEMS".to_string(),
|
"WEYLAND-YUTANI SYSTEMS".to_string(),
|
||||||
"MU/TH/UR 6000 - INTERFACE 2.4.1".to_string(),
|
"MU/TH/UR 6000 - INTERFACE 2.4.1".to_string(),
|
||||||
@@ -380,6 +383,12 @@ impl RetroTui {
|
|||||||
// Set animation target based on processing state
|
// Set animation target based on processing state
|
||||||
state.activity_animation_target = if state.is_processing { 1.0 } else { 0.0 };
|
state.activity_animation_target = if state.is_processing { 1.0 } else { 0.0 };
|
||||||
|
|
||||||
|
// Clear input buffer when entering PROCESSING mode
|
||||||
|
if !was_processing && state.is_processing {
|
||||||
|
state.input_buffer.clear();
|
||||||
|
state.cursor_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove cursor when exiting PROCESSING mode
|
// Remove cursor when exiting PROCESSING mode
|
||||||
if was_processing && !state.is_processing {
|
if was_processing && !state.is_processing {
|
||||||
if let Some(last) = state.output_history.last_mut() {
|
if let Some(last) = state.output_history.last_mut() {
|
||||||
@@ -543,7 +552,7 @@ impl RetroTui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw header/input area
|
// Draw header/input area
|
||||||
Self::draw_input_area(f, chunks[0], &state.input_buffer, state.cursor_blink);
|
Self::draw_input_area(f, chunks[0], &state.input_buffer, state.cursor_position, state.cursor_blink, state.is_processing);
|
||||||
|
|
||||||
// Draw main output area
|
// Draw main output area
|
||||||
Self::draw_output_area(f, chunks[1], &state.output_history, state.scroll_offset);
|
Self::draw_output_area(f, chunks[1], &state.output_history, state.scroll_offset);
|
||||||
@@ -575,15 +584,63 @@ impl RetroTui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the input area with prompt
|
/// Draw the input area with prompt
|
||||||
fn draw_input_area(f: &mut Frame, area: Rect, input_buffer: &str, cursor_blink: bool) {
|
fn draw_input_area(f: &mut Frame, area: Rect, input_buffer: &str, cursor_position: usize, cursor_blink: bool, is_processing: bool) {
|
||||||
// Show the actual input buffer content with prompt
|
let prompt = "g3> ";
|
||||||
let input_text = if cursor_blink {
|
let prompt_len = prompt.len();
|
||||||
format!("g3> {}█", input_buffer)
|
|
||||||
} else {
|
|
||||||
format!("g3> {} ", input_buffer)
|
|
||||||
};
|
|
||||||
|
|
||||||
let input = Paragraph::new(input_text)
|
// Calculate available width for text (accounting for borders and prompt)
|
||||||
|
let available_width = area.width.saturating_sub(2).saturating_sub(prompt_len as u16) as usize;
|
||||||
|
|
||||||
|
// Don't show cursor if processing
|
||||||
|
let show_cursor = !is_processing && cursor_blink;
|
||||||
|
|
||||||
|
// Build the display text with cursor at the right position
|
||||||
|
let mut display_text = String::new();
|
||||||
|
display_text.push_str(prompt);
|
||||||
|
|
||||||
|
if input_buffer.is_empty() {
|
||||||
|
// Empty buffer - just show cursor if applicable
|
||||||
|
if show_cursor {
|
||||||
|
display_text.push('█');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Calculate which part of the buffer to show (handle wrapping)
|
||||||
|
let total_cursor_pos = cursor_position;
|
||||||
|
|
||||||
|
// Determine the window into the buffer we should show
|
||||||
|
let window_start = if total_cursor_pos > available_width - 1 {
|
||||||
|
// Cursor is beyond the visible area, scroll the view
|
||||||
|
total_cursor_pos - (available_width - 1)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the visible portion of the buffer
|
||||||
|
let visible_buffer: String = input_buffer
|
||||||
|
.chars()
|
||||||
|
.skip(window_start)
|
||||||
|
.take(available_width)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Insert cursor at the appropriate position in the visible text
|
||||||
|
let visible_cursor_pos = cursor_position.saturating_sub(window_start);
|
||||||
|
|
||||||
|
for (i, ch) in visible_buffer.chars().enumerate() {
|
||||||
|
if i == visible_cursor_pos && show_cursor {
|
||||||
|
display_text.push('█');
|
||||||
|
// Don't add the character under the cursor if we're showing the block cursor
|
||||||
|
} else {
|
||||||
|
display_text.push(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If cursor is at the end and we're showing it
|
||||||
|
if visible_cursor_pos == visible_buffer.len() && show_cursor {
|
||||||
|
display_text.push('█');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = Paragraph::new(display_text)
|
||||||
.style(Style::default().fg(TERMINAL_GREEN))
|
.style(Style::default().fg(TERMINAL_GREEN))
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
@@ -1092,6 +1149,116 @@ impl RetroTui {
|
|||||||
pub fn update_input(&self, input: &str) {
|
pub fn update_input(&self, input: &str) {
|
||||||
if let Ok(mut state) = self.state.lock() {
|
if let Ok(mut state) = self.state.lock() {
|
||||||
state.input_buffer = input.to_string();
|
state.input_buffer = input.to_string();
|
||||||
|
// Keep cursor at end when updating the whole buffer
|
||||||
|
state.cursor_position = input.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move cursor left
|
||||||
|
pub fn cursor_left(&self) {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
if state.cursor_position > 0 {
|
||||||
|
state.cursor_position -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move cursor right
|
||||||
|
pub fn cursor_right(&self) {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
if state.cursor_position < state.input_buffer.len() {
|
||||||
|
state.cursor_position += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move cursor to beginning of line (Ctrl-A)
|
||||||
|
pub fn cursor_home(&self) {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
state.cursor_position = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move cursor to end of line (Ctrl-E)
|
||||||
|
pub fn cursor_end(&self) {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
state.cursor_position = state.input_buffer.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete word before cursor (Ctrl-W)
|
||||||
|
pub fn delete_word(&self) {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
if state.cursor_position > 0 {
|
||||||
|
// Find the start of the word to delete
|
||||||
|
let mut word_start = state.cursor_position;
|
||||||
|
let chars: Vec<char> = state.input_buffer.chars().collect();
|
||||||
|
|
||||||
|
// Skip trailing spaces
|
||||||
|
while word_start > 0 && chars[word_start - 1].is_whitespace() {
|
||||||
|
word_start -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find word boundary
|
||||||
|
while word_start > 0 && !chars[word_start - 1].is_whitespace() {
|
||||||
|
word_start -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the word
|
||||||
|
let before = state.input_buffer.chars().take(word_start).collect::<String>();
|
||||||
|
let after = state.input_buffer.chars().skip(state.cursor_position).collect::<String>();
|
||||||
|
state.input_buffer = format!("{}{}", before, after);
|
||||||
|
state.cursor_position = word_start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete from cursor to end of line (Ctrl-K)
|
||||||
|
pub fn delete_to_end(&self) {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
state.input_buffer = state.input_buffer.chars().take(state.cursor_position).collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current input buffer and cursor position
|
||||||
|
pub fn get_input_state(&self) -> (String, usize) {
|
||||||
|
if let Ok(state) = self.state.lock() {
|
||||||
|
(state.input_buffer.clone(), state.cursor_position)
|
||||||
|
} else {
|
||||||
|
(String::new(), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert character at cursor position
|
||||||
|
pub fn insert_char(&self, ch: char) {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
let before = state.input_buffer.chars().take(state.cursor_position).collect::<String>();
|
||||||
|
let after = state.input_buffer.chars().skip(state.cursor_position).collect::<String>();
|
||||||
|
state.input_buffer = format!("{}{}{}", before, ch, after);
|
||||||
|
state.cursor_position += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete character at cursor position (Delete key)
|
||||||
|
pub fn delete_char(&self) {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
if state.cursor_position < state.input_buffer.len() {
|
||||||
|
let before = state.input_buffer.chars().take(state.cursor_position).collect::<String>();
|
||||||
|
let after = state.input_buffer.chars().skip(state.cursor_position + 1).collect::<String>();
|
||||||
|
state.input_buffer = format!("{}{}", before, after);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete character before cursor (Backspace)
|
||||||
|
pub fn backspace(&self) {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
if state.cursor_position > 0 {
|
||||||
|
let before = state.input_buffer.chars().take(state.cursor_position - 1).collect::<String>();
|
||||||
|
let after = state.input_buffer.chars().skip(state.cursor_position).collect::<String>();
|
||||||
|
state.input_buffer = format!("{}{}", before, after);
|
||||||
|
state.cursor_position -= 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user