feat(claudbg-pow4): add cursor movement to filter/search bar input

Add filter_cursor and search_cursor fields to AppState; handle Left/Right
to move cursor, Home/End to jump, cursor-aware Backspace (deletes before
cursor), and cursor-aware character insertion. Render the cursor at the
correct mid-text position. Update help modal to document new bindings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
Elijah Voigt 2 months ago
parent b710a775fa
commit 3e5125b90b

@ -1,10 +1,11 @@
--- ---
# claudbg-pow4 # claudbg-pow4
title: 'TUI: cursor movement in filter/search bar (left/right arrow keys)' title: 'TUI: cursor movement in filter/search bar (left/right arrow keys)'
status: todo status: in-progress
type: feature type: feature
priority: normal
created_at: 2026-04-01T16:47:09Z created_at: 2026-04-01T16:47:09Z
updated_at: 2026-04-01T16:47:09Z updated_at: 2026-04-01T17:00:18Z
--- ---
Support moving the cursor left and right within the Filter/Search bar text input so users can edit text in the middle of the input without having to delete everything first. Support moving the cursor left and right within the Filter/Search bar text input so users can edit text in the middle of the input without having to delete everything first.

@ -16,7 +16,7 @@ use crate::tui::state::AppState;
/// Dialog dimensions. /// Dialog dimensions.
const DIALOG_WIDTH: u16 = 36; const DIALOG_WIDTH: u16 = 36;
const DIALOG_HEIGHT: u16 = 38; const DIALOG_HEIGHT: u16 = 42;
/// Compute a centered [`Rect`] of the given size within `area`. /// Compute a centered [`Rect`] of the given size within `area`.
fn centered_rect(width: u16, height: u16, area: Rect) -> Rect { fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {
@ -48,6 +48,8 @@ const HELP_TEXT: &str = "\
\n\ \n\
Filter (sessions)\n\ Filter (sessions)\n\
t / / open filter\n\ t / / open filter\n\
/ move cursor\n\
Home/End jump to start/end\n\
Enter apply & close\n\ Enter apply & close\n\
Esc clear input\n\ Esc clear input\n\
\n\ \n\
@ -64,6 +66,8 @@ const HELP_TEXT: &str = "\
\n\ \n\
Search (transcript)\n\ Search (transcript)\n\
t / / open search\n\ t / / open search\n\
/ move cursor\n\
Home/End jump to start/end\n\
n / N next/prev match\n\ n / N next/prev match\n\
Enter apply & close\n\ Enter apply & close\n\
Esc clear & close\n\ Esc clear & close\n\

@ -175,9 +175,30 @@ fn render_filter_bar(f: &mut Frame, area: Rect, state: &AppState) {
// Build the content line: label + input text (+ cursor when focused). // Build the content line: label + input text (+ cursor when focused).
let label = Span::styled("Filter: ", Style::default().add_modifier(Modifier::BOLD)); let label = Span::styled("Filter: ", Style::default().add_modifier(Modifier::BOLD));
let input_text = if focused { let input_text = if focused {
// Show a block cursor at end of input. // Split the input at the cursor position and insert a block cursor glyph.
let mut spans = vec![label, Span::raw(state.filter_input.clone())]; let cursor = state.filter_cursor.min(state.filter_input.len());
spans.push(Span::styled("█", Style::default().fg(Color::Yellow))); let before = &state.filter_input[..cursor];
let after = &state.filter_input[cursor..];
// The cursor glyph replaces the character under it (or is appended when at end).
let (cursor_char, after_rest) = if after.is_empty() {
("█", "")
} else {
let first_char_end = after
.char_indices()
.nth(1)
.map(|(i, _)| i)
.unwrap_or(after.len());
(&after[..first_char_end], &after[first_char_end..])
};
let spans = vec![
label,
Span::raw(before.to_string()),
Span::styled(
cursor_char,
Style::default().fg(Color::Black).bg(Color::Yellow),
),
Span::raw(after_rest.to_string()),
];
Line::from(spans) Line::from(spans)
} else if state.filter_input.is_empty() && state.filter_active.is_empty() { } else if state.filter_input.is_empty() && state.filter_active.is_empty() {
Line::from(vec![ Line::from(vec![
@ -262,11 +283,13 @@ pub fn handle_session_list_event(event: Event, state: &mut AppState) -> bool {
// Focus the filter input directly. // Focus the filter input directly.
KeyCode::Char('t') | KeyCode::Char('/') => { KeyCode::Char('t') | KeyCode::Char('/') => {
state.focus = Focus::FilterInput; state.focus = Focus::FilterInput;
state.filter_cursor = state.filter_input.len();
true true
} }
// Tab cycles focus: list → filter → list. // Tab cycles focus: list → filter → list.
KeyCode::Tab => { KeyCode::Tab => {
state.focus = Focus::FilterInput; state.focus = Focus::FilterInput;
state.filter_cursor = state.filter_input.len();
true true
} }
// Quit — show confirmation dialog rather than exiting immediately. // Quit — show confirmation dialog rather than exiting immediately.
@ -288,18 +311,66 @@ pub fn handle_session_list_event(event: Event, state: &mut AppState) -> bool {
/// Returns `true` when the event is consumed. /// Returns `true` when the event is consumed.
fn handle_filter_input_event(code: KeyCode, state: &mut AppState) -> bool { fn handle_filter_input_event(code: KeyCode, state: &mut AppState) -> bool {
match code { match code {
// Character input: append to filter_input and reset history browsing. // Character input: insert at cursor position and reset history browsing.
KeyCode::Char(c) => { KeyCode::Char(c) => {
state.filter_input.push(c); let pos = state.filter_cursor;
state.filter_input.insert(pos, c);
state.filter_cursor += c.len_utf8();
state.filter_history_pos = None; state.filter_history_pos = None;
true true
} }
// Backspace: remove last char. // Backspace: remove the character *before* the cursor.
KeyCode::Backspace => { KeyCode::Backspace => {
state.filter_input.pop(); if state.filter_cursor > 0 {
// Find the start of the previous UTF-8 character.
let pos = state.filter_cursor;
let prev = state.filter_input[..pos]
.char_indices()
.next_back()
.map(|(i, _)| i)
.unwrap_or(0);
state.filter_input.remove(prev);
state.filter_cursor = prev;
}
state.filter_history_pos = None; state.filter_history_pos = None;
true true
} }
// Left arrow: move cursor one character to the left.
KeyCode::Left => {
if state.filter_cursor > 0 {
let pos = state.filter_cursor;
let prev = state.filter_input[..pos]
.char_indices()
.next_back()
.map(|(i, _)| i)
.unwrap_or(0);
state.filter_cursor = prev;
}
true
}
// Right arrow: move cursor one character to the right.
KeyCode::Right => {
let pos = state.filter_cursor;
if pos < state.filter_input.len() {
let next = state.filter_input[pos..]
.char_indices()
.nth(1)
.map(|(i, _)| pos + i)
.unwrap_or(state.filter_input.len());
state.filter_cursor = next;
}
true
}
// Home: jump cursor to start of input.
KeyCode::Home => {
state.filter_cursor = 0;
true
}
// End: jump cursor to end of input.
KeyCode::End => {
state.filter_cursor = state.filter_input.len();
true
}
// Enter: apply the current input as the active filter and return focus to the list. // Enter: apply the current input as the active filter and return focus to the list.
KeyCode::Enter => { KeyCode::Enter => {
let query = state.filter_input.trim().to_string(); let query = state.filter_input.trim().to_string();
@ -326,6 +397,7 @@ fn handle_filter_input_event(code: KeyCode, state: &mut AppState) -> bool {
// Escape: clear the text input (but keep the panel visible). // Escape: clear the text input (but keep the panel visible).
KeyCode::Esc => { KeyCode::Esc => {
state.filter_input.clear(); state.filter_input.clear();
state.filter_cursor = 0;
state.filter_history_pos = None; state.filter_history_pos = None;
true true
} }
@ -340,6 +412,7 @@ fn handle_filter_input_event(code: KeyCode, state: &mut AppState) -> bool {
}; };
state.filter_history_pos = Some(new_pos); state.filter_history_pos = Some(new_pos);
state.filter_input = state.filter_history[new_pos].clone(); state.filter_input = state.filter_history[new_pos].clone();
state.filter_cursor = state.filter_input.len();
true true
} }
// Down arrow: browse forward through history (or clear when past the end). // Down arrow: browse forward through history (or clear when past the end).
@ -351,10 +424,12 @@ fn handle_filter_input_event(code: KeyCode, state: &mut AppState) -> bool {
let new_pos = pos + 1; let new_pos = pos + 1;
state.filter_history_pos = Some(new_pos); state.filter_history_pos = Some(new_pos);
state.filter_input = state.filter_history[new_pos].clone(); state.filter_input = state.filter_history[new_pos].clone();
state.filter_cursor = state.filter_input.len();
} else { } else {
// Past the end: clear input and stop browsing history. // Past the end: clear input and stop browsing history.
state.filter_history_pos = None; state.filter_history_pos = None;
state.filter_input.clear(); state.filter_input.clear();
state.filter_cursor = 0;
} }
true true
} }
@ -642,8 +717,10 @@ mod tests {
let mut state = AppState::new(); let mut state = AppState::new();
state.focus = Focus::FilterInput; state.focus = Focus::FilterInput;
state.filter_input = "mo".to_string(); state.filter_input = "mo".to_string();
state.filter_cursor = 2; // cursor at end
handle_session_list_event(press(KeyCode::Backspace), &mut state); handle_session_list_event(press(KeyCode::Backspace), &mut state);
assert_eq!(state.filter_input, "m"); assert_eq!(state.filter_input, "m");
assert_eq!(state.filter_cursor, 1);
} }
#[test] #[test]
@ -729,4 +806,110 @@ mod tests {
assert_eq!(state.filter_input, ""); assert_eq!(state.filter_input, "");
assert_eq!(state.filter_history_pos, None); assert_eq!(state.filter_history_pos, None);
} }
// ── Filter cursor movement ───────────────────────────────────────────────
#[test]
fn char_inserts_at_cursor_not_end() {
let mut state = AppState::new();
state.focus = Focus::FilterInput;
state.filter_input = "ac".to_string();
state.filter_cursor = 1; // cursor between 'a' and 'c'
handle_session_list_event(press(KeyCode::Char('b')), &mut state);
assert_eq!(state.filter_input, "abc");
assert_eq!(state.filter_cursor, 2);
}
#[test]
fn backspace_deletes_before_cursor_not_end() {
let mut state = AppState::new();
state.focus = Focus::FilterInput;
state.filter_input = "abc".to_string();
state.filter_cursor = 2; // cursor between 'b' and 'c'
handle_session_list_event(press(KeyCode::Backspace), &mut state);
assert_eq!(state.filter_input, "ac");
assert_eq!(state.filter_cursor, 1);
}
#[test]
fn backspace_at_start_does_nothing() {
let mut state = AppState::new();
state.focus = Focus::FilterInput;
state.filter_input = "abc".to_string();
state.filter_cursor = 0;
handle_session_list_event(press(KeyCode::Backspace), &mut state);
assert_eq!(state.filter_input, "abc");
assert_eq!(state.filter_cursor, 0);
}
#[test]
fn left_moves_cursor_back() {
let mut state = AppState::new();
state.focus = Focus::FilterInput;
state.filter_input = "abc".to_string();
state.filter_cursor = 2;
handle_session_list_event(press(KeyCode::Left), &mut state);
assert_eq!(state.filter_cursor, 1);
}
#[test]
fn left_clamps_at_zero() {
let mut state = AppState::new();
state.focus = Focus::FilterInput;
state.filter_input = "abc".to_string();
state.filter_cursor = 0;
handle_session_list_event(press(KeyCode::Left), &mut state);
assert_eq!(state.filter_cursor, 0);
}
#[test]
fn right_moves_cursor_forward() {
let mut state = AppState::new();
state.focus = Focus::FilterInput;
state.filter_input = "abc".to_string();
state.filter_cursor = 1;
handle_session_list_event(press(KeyCode::Right), &mut state);
assert_eq!(state.filter_cursor, 2);
}
#[test]
fn right_clamps_at_end() {
let mut state = AppState::new();
state.focus = Focus::FilterInput;
state.filter_input = "abc".to_string();
state.filter_cursor = 3;
handle_session_list_event(press(KeyCode::Right), &mut state);
assert_eq!(state.filter_cursor, 3);
}
#[test]
fn home_jumps_to_start() {
let mut state = AppState::new();
state.focus = Focus::FilterInput;
state.filter_input = "abc".to_string();
state.filter_cursor = 3;
handle_session_list_event(press(KeyCode::Home), &mut state);
assert_eq!(state.filter_cursor, 0);
}
#[test]
fn end_jumps_to_end() {
let mut state = AppState::new();
state.focus = Focus::FilterInput;
state.filter_input = "abc".to_string();
state.filter_cursor = 0;
handle_session_list_event(press(KeyCode::End), &mut state);
assert_eq!(state.filter_cursor, 3);
}
#[test]
fn esc_resets_cursor_to_zero() {
let mut state = AppState::new();
state.focus = Focus::FilterInput;
state.filter_input = "abc".to_string();
state.filter_cursor = 2;
handle_session_list_event(press(KeyCode::Esc), &mut state);
assert_eq!(state.filter_input, "");
assert_eq!(state.filter_cursor, 0);
}
} }

@ -379,8 +379,29 @@ fn render_search_bar(f: &mut Frame, area: Rect, state: &AppState) {
let label = Span::styled("Search: ", Style::default().add_modifier(Modifier::BOLD)); let label = Span::styled("Search: ", Style::default().add_modifier(Modifier::BOLD));
let input_line = if focused { let input_line = if focused {
let mut spans = vec![label, Span::raw(state.search_input.clone())]; // Split the input at the cursor position and insert a block cursor glyph.
spans.push(Span::styled("█", Style::default().fg(Color::Yellow))); let cursor = state.search_cursor.min(state.search_input.len());
let before = &state.search_input[..cursor];
let after = &state.search_input[cursor..];
let (cursor_char, after_rest) = if after.is_empty() {
("█", "")
} else {
let first_char_end = after
.char_indices()
.nth(1)
.map(|(i, _)| i)
.unwrap_or(after.len());
(&after[..first_char_end], &after[first_char_end..])
};
let spans = vec![
label,
Span::raw(before.to_string()),
Span::styled(
cursor_char,
Style::default().fg(Color::Black).bg(Color::Yellow),
),
Span::raw(after_rest.to_string()),
];
Line::from(spans) Line::from(spans)
} else if state.search_input.is_empty() && state.search_active.is_empty() { } else if state.search_input.is_empty() && state.search_active.is_empty() {
Line::from(vec![ Line::from(vec![
@ -568,6 +589,10 @@ pub fn handle_transcript_event(event: Event, state: &mut AppState) -> bool {
Focus::SubagentsPanel => Focus::SearchInput, Focus::SubagentsPanel => Focus::SearchInput,
Focus::SearchInput | Focus::FilterInput => Focus::ChatLog, Focus::SearchInput | Focus::FilterInput => Focus::ChatLog,
}; };
// When switching into the search bar, position the cursor at the end.
if state.focus == Focus::SearchInput {
state.search_cursor = state.search_input.len();
}
true true
} }
// Navigate back: from SubagentTranscript → parent session transcript; // Navigate back: from SubagentTranscript → parent session transcript;
@ -596,6 +621,7 @@ pub fn handle_transcript_event(event: Event, state: &mut AppState) -> bool {
// Jump directly to the search input. // Jump directly to the search input.
KeyCode::Char('t') | KeyCode::Char('/') => { KeyCode::Char('t') | KeyCode::Char('/') => {
state.focus = Focus::SearchInput; state.focus = Focus::SearchInput;
state.search_cursor = state.search_input.len();
true true
} }
// Next match. // Next match.
@ -726,14 +752,61 @@ pub fn handle_transcript_event(event: Event, state: &mut AppState) -> bool {
/// Returns `true` when the event is consumed. /// Returns `true` when the event is consumed.
fn handle_search_input_event(code: KeyCode, state: &mut AppState) -> bool { fn handle_search_input_event(code: KeyCode, state: &mut AppState) -> bool {
match code { match code {
// Character input: append to search_input. // Character input: insert at cursor position.
KeyCode::Char(c) => { KeyCode::Char(c) => {
state.search_input.push(c); let pos = state.search_cursor;
state.search_input.insert(pos, c);
state.search_cursor += c.len_utf8();
true true
} }
// Backspace: remove last char. // Backspace: remove the character *before* the cursor.
KeyCode::Backspace => { KeyCode::Backspace => {
state.search_input.pop(); if state.search_cursor > 0 {
let pos = state.search_cursor;
let prev = state.search_input[..pos]
.char_indices()
.next_back()
.map(|(i, _)| i)
.unwrap_or(0);
state.search_input.remove(prev);
state.search_cursor = prev;
}
true
}
// Left arrow: move cursor one character to the left.
KeyCode::Left => {
if state.search_cursor > 0 {
let pos = state.search_cursor;
let prev = state.search_input[..pos]
.char_indices()
.next_back()
.map(|(i, _)| i)
.unwrap_or(0);
state.search_cursor = prev;
}
true
}
// Right arrow: move cursor one character to the right.
KeyCode::Right => {
let pos = state.search_cursor;
if pos < state.search_input.len() {
let next = state.search_input[pos..]
.char_indices()
.nth(1)
.map(|(i, _)| pos + i)
.unwrap_or(state.search_input.len());
state.search_cursor = next;
}
true
}
// Home: jump cursor to start of input.
KeyCode::Home => {
state.search_cursor = 0;
true
}
// End: jump cursor to end of input.
KeyCode::End => {
state.search_cursor = state.search_input.len();
true true
} }
// Enter: apply the search, compute match lines, return focus to ChatLog. // Enter: apply the search, compute match lines, return focus to ChatLog.
@ -753,6 +826,7 @@ fn handle_search_input_event(code: KeyCode, state: &mut AppState) -> bool {
// Escape: clear search and return focus to ChatLog. // Escape: clear search and return focus to ChatLog.
KeyCode::Esc => { KeyCode::Esc => {
state.search_input.clear(); state.search_input.clear();
state.search_cursor = 0;
state.search_active.clear(); state.search_active.clear();
state.search_match_lines.clear(); state.search_match_lines.clear();
state.search_current_match = 0; state.search_current_match = 0;
@ -1337,8 +1411,10 @@ mod tests {
let mut state = transcript_state(); let mut state = transcript_state();
state.focus = Focus::SearchInput; state.focus = Focus::SearchInput;
state.search_input = "hi".to_string(); state.search_input = "hi".to_string();
state.search_cursor = 2; // cursor at end
handle_transcript_event(press(KeyCode::Backspace), &mut state); handle_transcript_event(press(KeyCode::Backspace), &mut state);
assert_eq!(state.search_input, "h"); assert_eq!(state.search_input, "h");
assert_eq!(state.search_cursor, 1);
} }
#[test] #[test]
@ -1492,4 +1568,110 @@ mod tests {
let consumed = handle_transcript_event(Event::FocusGained, &mut state); let consumed = handle_transcript_event(Event::FocusGained, &mut state);
assert!(!consumed); assert!(!consumed);
} }
// ── Search cursor movement ────────────────────────────────────────────────
#[test]
fn search_char_inserts_at_cursor_not_end() {
let mut state = transcript_state();
state.focus = Focus::SearchInput;
state.search_input = "ac".to_string();
state.search_cursor = 1; // between 'a' and 'c'
handle_transcript_event(press(KeyCode::Char('b')), &mut state);
assert_eq!(state.search_input, "abc");
assert_eq!(state.search_cursor, 2);
}
#[test]
fn search_backspace_deletes_before_cursor_not_end() {
let mut state = transcript_state();
state.focus = Focus::SearchInput;
state.search_input = "abc".to_string();
state.search_cursor = 2; // between 'b' and 'c'
handle_transcript_event(press(KeyCode::Backspace), &mut state);
assert_eq!(state.search_input, "ac");
assert_eq!(state.search_cursor, 1);
}
#[test]
fn search_backspace_at_start_does_nothing() {
let mut state = transcript_state();
state.focus = Focus::SearchInput;
state.search_input = "abc".to_string();
state.search_cursor = 0;
handle_transcript_event(press(KeyCode::Backspace), &mut state);
assert_eq!(state.search_input, "abc");
assert_eq!(state.search_cursor, 0);
}
#[test]
fn search_left_moves_cursor_back() {
let mut state = transcript_state();
state.focus = Focus::SearchInput;
state.search_input = "abc".to_string();
state.search_cursor = 2;
handle_transcript_event(press(KeyCode::Left), &mut state);
assert_eq!(state.search_cursor, 1);
}
#[test]
fn search_left_clamps_at_zero() {
let mut state = transcript_state();
state.focus = Focus::SearchInput;
state.search_input = "abc".to_string();
state.search_cursor = 0;
handle_transcript_event(press(KeyCode::Left), &mut state);
assert_eq!(state.search_cursor, 0);
}
#[test]
fn search_right_moves_cursor_forward() {
let mut state = transcript_state();
state.focus = Focus::SearchInput;
state.search_input = "abc".to_string();
state.search_cursor = 1;
handle_transcript_event(press(KeyCode::Right), &mut state);
assert_eq!(state.search_cursor, 2);
}
#[test]
fn search_right_clamps_at_end() {
let mut state = transcript_state();
state.focus = Focus::SearchInput;
state.search_input = "abc".to_string();
state.search_cursor = 3;
handle_transcript_event(press(KeyCode::Right), &mut state);
assert_eq!(state.search_cursor, 3);
}
#[test]
fn search_home_jumps_to_start() {
let mut state = transcript_state();
state.focus = Focus::SearchInput;
state.search_input = "abc".to_string();
state.search_cursor = 3;
handle_transcript_event(press(KeyCode::Home), &mut state);
assert_eq!(state.search_cursor, 0);
}
#[test]
fn search_end_jumps_to_end() {
let mut state = transcript_state();
state.focus = Focus::SearchInput;
state.search_input = "abc".to_string();
state.search_cursor = 0;
handle_transcript_event(press(KeyCode::End), &mut state);
assert_eq!(state.search_cursor, 3);
}
#[test]
fn search_esc_resets_cursor_to_zero() {
let mut state = transcript_state();
state.focus = Focus::SearchInput;
state.search_input = "abc".to_string();
state.search_cursor = 2;
handle_transcript_event(press(KeyCode::Esc), &mut state);
assert_eq!(state.search_input, "");
assert_eq!(state.search_cursor, 0);
}
} }

@ -132,6 +132,11 @@ pub struct AppState {
// ── Search ────────────────────────────────────────────────────────────── // ── Search ──────────────────────────────────────────────────────────────
/// Text currently being typed in the transcript search box. /// Text currently being typed in the transcript search box.
pub search_input: String, pub search_input: String,
/// Cursor position (byte offset) within `search_input`.
///
/// Always points to a valid UTF-8 char boundary. Ranges from `0`
/// (before the first character) to `search_input.len()` (after the last).
pub search_cursor: usize,
/// The last applied search query (highlights all case-insensitive matches). /// The last applied search query (highlights all case-insensitive matches).
/// Empty string means no search is active. /// Empty string means no search is active.
pub search_active: String, pub search_active: String,
@ -154,6 +159,11 @@ pub struct AppState {
// ── Filter ────────────────────────────────────────────────────────────── // ── Filter ──────────────────────────────────────────────────────────────
/// Text currently being typed in the filter input box. /// Text currently being typed in the filter input box.
pub filter_input: String, pub filter_input: String,
/// Cursor position (byte offset) within `filter_input`.
///
/// Always points to a valid UTF-8 char boundary. Ranges from `0`
/// (before the first character) to `filter_input.len()` (after the last).
pub filter_cursor: usize,
/// The last successfully applied filter query string. /// The last successfully applied filter query string.
/// Empty string means no filter is active. /// Empty string means no filter is active.
pub filter_active: String, pub filter_active: String,
@ -248,6 +258,7 @@ impl AppState {
transcript_h_scroll: 0, transcript_h_scroll: 0,
transcript_page_height: 0, transcript_page_height: 0,
search_input: String::new(), search_input: String::new(),
search_cursor: 0,
search_active: String::new(), search_active: String::new(),
search_match_lines: Vec::new(), search_match_lines: Vec::new(),
search_current_match: 0, search_current_match: 0,
@ -257,6 +268,7 @@ impl AppState {
show_quit_dialog: false, show_quit_dialog: false,
show_help: false, show_help: false,
filter_input: String::new(), filter_input: String::new(),
filter_cursor: 0,
filter_active: String::new(), filter_active: String::new(),
filter_history, filter_history,
filter_history_pos: None, filter_history_pos: None,
@ -281,6 +293,7 @@ impl AppState {
self.transcript_scroll = 0; self.transcript_scroll = 0;
self.transcript_h_scroll = 0; self.transcript_h_scroll = 0;
self.search_input.clear(); self.search_input.clear();
self.search_cursor = 0;
self.search_active.clear(); self.search_active.clear();
self.search_match_lines.clear(); self.search_match_lines.clear();
self.search_current_match = 0; self.search_current_match = 0;
@ -306,6 +319,7 @@ impl AppState {
self.transcript_scroll = 0; self.transcript_scroll = 0;
self.transcript_h_scroll = 0; self.transcript_h_scroll = 0;
self.search_input.clear(); self.search_input.clear();
self.search_cursor = 0;
self.search_active.clear(); self.search_active.clear();
self.search_match_lines.clear(); self.search_match_lines.clear();
self.search_current_match = 0; self.search_current_match = 0;
@ -325,6 +339,7 @@ impl AppState {
self.transcript_scroll = 0; self.transcript_scroll = 0;
self.transcript_h_scroll = 0; self.transcript_h_scroll = 0;
self.search_input.clear(); self.search_input.clear();
self.search_cursor = 0;
self.search_active.clear(); self.search_active.clear();
self.search_match_lines.clear(); self.search_match_lines.clear();
self.search_current_match = 0; self.search_current_match = 0;
@ -344,6 +359,7 @@ impl AppState {
self.transcript_scroll = 0; self.transcript_scroll = 0;
self.transcript_h_scroll = 0; self.transcript_h_scroll = 0;
self.search_input.clear(); self.search_input.clear();
self.search_cursor = 0;
self.search_active.clear(); self.search_active.clear();
self.search_match_lines.clear(); self.search_match_lines.clear();
self.search_current_match = 0; self.search_current_match = 0;

Loading…
Cancel
Save