diff --git a/src/command/client/search.rs b/src/command/client/search.rs index de6e796..e1ee55d 100644 --- a/src/command/client/search.rs +++ b/src/command/client/search.rs @@ -110,6 +110,8 @@ impl Cmd { struct State { input: String, + cursor_index: usize, + filter_mode: FilterMode, results: Vec, @@ -277,6 +279,31 @@ async fn query_results( Ok(()) } +fn get_input_prefix(app: &mut State, i: usize) -> String { + return app.input.chars().take(i).collect(); +} +fn get_input_suffix(app: &mut State, i: usize) -> String { + return app.input.chars().skip(i).collect(); +} + +fn insert_char_into_input(app: &mut State, i: usize, c: char) { + let mut result = String::from(""); + result.push_str(&get_input_prefix(app, i)); + result.push_str(&c.to_string()); + result.push_str(&get_input_suffix(app, i)); + app.input = result; +} + +fn remove_char_from_input(app: &mut State, i: usize) -> char { + let mut result = String::from(""); + result.push_str(&get_input_prefix(app, i - 1)); + result.push_str(&get_input_suffix(app, i)); + let c = app.input.chars().nth(i - 1).unwrap(); + app.input = result; + c +} + +#[allow(clippy::too_many_lines)] async fn key_handler( input: Key, search_mode: SearchMode, @@ -304,29 +331,56 @@ async fn key_handler( .map_or(app.input.clone(), |h| h.command.clone()), ); } + Key::Left | Key::Ctrl('h') => { + if app.cursor_index != 0 { + app.cursor_index -= 1; + } + } + Key::Right | Key::Ctrl('l') => { + if app.cursor_index < app.input.width() { + app.cursor_index += 1; + } + } + Key::Ctrl('a') => { + app.cursor_index = 0; + } + Key::Ctrl('e') => { + app.cursor_index = app.input.chars().count(); + } Key::Char(c) => { - app.input.push(c); + insert_char_into_input(app, app.cursor_index, c); + app.cursor_index += 1; query_results(app, search_mode, db).await.unwrap(); } Key::Backspace => { - app.input.pop(); - query_results(app, search_mode, db).await.unwrap(); - } - // \u{7f} is escape sequence for backspace - Key::Alt('\u{7f}') => { - let words: Vec<&str> = app.input.split(' ').collect(); - if words.is_empty() { + if app.cursor_index == 0 { return None; } - if words.len() == 1 { - app.input = String::from(""); - } else { - app.input = words[0..(words.len() - 1)].join(" "); + remove_char_from_input(app, app.cursor_index); + app.cursor_index -= 1; + query_results(app, search_mode, db).await.unwrap(); + } + Key::Ctrl('w') => { + let mut stop_on_next_whitespace = false; + loop { + if app.cursor_index == 0 { + break; + } + if app.input.chars().nth(app.cursor_index - 1) == Some(' ') + && stop_on_next_whitespace + { + break; + } + if !remove_char_from_input(app, app.cursor_index).is_whitespace() { + stop_on_next_whitespace = true; + } + app.cursor_index -= 1; } query_results(app, search_mode, db).await.unwrap(); } Key::Ctrl('u') => { app.input = String::from(""); + app.cursor_index = 0; query_results(app, search_mode, db).await.unwrap(); } Key::Ctrl('r') => { @@ -442,9 +496,16 @@ fn draw(f: &mut Frame<'_, T>, history_count: i64, app: &mut State) { ); f.render_widget(input, chunks[2]); + let width = UnicodeWidthStr::width( + app.input + .chars() + .take(app.cursor_index) + .collect::() + .as_str(), + ); f.set_cursor( // Put cursor past the end of the input text - chunks[2].x + app.input.width() as u16 + 1, + chunks[2].x + width as u16 + 1, // Move one line down, from the border to the input line chunks[2].y + 1, ); @@ -514,7 +575,13 @@ fn draw_compact(f: &mut Frame<'_, T>, history_count: i64, app: &mut app.render_results(f, chunks[1], Block::default()); f.render_widget(input, chunks[2]); - let extra_width = app.input.width() + filter_mode.len(); + let extra_width = UnicodeWidthStr::width( + app.input + .chars() + .take(app.cursor_index) + .collect::() + .as_str(), + ) + filter_mode.len(); f.set_cursor( // Put cursor past the end of the input text @@ -544,8 +611,12 @@ async fn select_history( // Setup event handlers let events = Events::new(); + let input = query.join(" "); + // Put the cursor at the end of the query by default + let cursor_index = input.chars().count(); let mut app = State { - input: query.join(" "), + input, + cursor_index, results: Vec::new(), results_state: ListState::default(), context: current_context(),