use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use tui_textarea::{Input, Key, TextArea};
use crate::app::{AppState, InputMode};
pub fn handle_key_event(app_state: &mut AppState, key: KeyEvent) {
match app_state.input_mode {
InputMode::Normal => {
if key.modifiers.contains(KeyModifiers::CONTROL)
|| key.modifiers.contains(KeyModifiers::SUPER)
{
handle_ctrl_key(app_state, key.code);
} else {
handle_normal_mode(app_state, key.code);
}
}
InputMode::Editing => handle_editing_mode(app_state, key),
InputMode::Command => handle_command_mode(app_state, key.code),
InputMode::CommandInLazyLoading => handle_command_in_lazy_loading_mode(app_state, key.code),
InputMode::SearchForward => handle_search_mode(app_state, key.code),
InputMode::SearchBackward => handle_search_mode(app_state, key.code),
InputMode::Help => handle_help_mode(app_state, key.code),
InputMode::LazyLoading => handle_lazy_loading_mode(app_state, key.code),
}
}
fn handle_ctrl_key(app_state: &mut AppState, key_code: KeyCode) {
match key_code {
KeyCode::Left => {
app_state.jump_to_prev_non_empty_cell_left();
}
KeyCode::Right => {
app_state.jump_to_prev_non_empty_cell_right();
}
KeyCode::Up => {
app_state.jump_to_prev_non_empty_cell_up();
}
KeyCode::Down => {
app_state.jump_to_prev_non_empty_cell_down();
}
KeyCode::Char('r') => {
if let Err(e) = app_state.redo() {
app_state.add_notification(format!("Redo failed: {e}"));
}
}
_ => {}
}
}
fn handle_command_mode(app_state: &mut AppState, key_code: KeyCode) {
match key_code {
KeyCode::Enter => app_state.execute_command(),
KeyCode::Esc => app_state.cancel_input(),
KeyCode::Backspace => app_state.delete_char_from_input(),
KeyCode::Char(c) => app_state.add_char_to_input(c),
_ => {}
}
}
fn handle_command_in_lazy_loading_mode(app_state: &mut AppState, key_code: KeyCode) {
match key_code {
KeyCode::Enter => {
let current_index = app_state.workbook.get_current_sheet_index();
let is_sheet_loaded = app_state.workbook.is_sheet_loaded(current_index);
app_state.execute_command();
if !is_sheet_loaded
&& !app_state
.workbook
.is_sheet_loaded(app_state.workbook.get_current_sheet_index())
&& matches!(app_state.input_mode, InputMode::Normal)
{
app_state.input_mode = InputMode::LazyLoading;
}
}
KeyCode::Esc => {
app_state.input_mode = InputMode::LazyLoading;
app_state.input_buffer = String::new();
}
KeyCode::Backspace => app_state.delete_char_from_input(),
KeyCode::Char(c) => app_state.add_char_to_input(c),
_ => {}
}
}
fn handle_normal_mode(app_state: &mut AppState, key_code: KeyCode) {
match key_code {
KeyCode::Enter => {
app_state.g_pressed = false;
let index = app_state.workbook.get_current_sheet_index();
let sheet_name = app_state.workbook.get_current_sheet_name();
if app_state.workbook.is_lazy_loading() && !app_state.workbook.is_sheet_loaded(index) {
if let Err(e) = app_state.workbook.ensure_sheet_loaded(index, &sheet_name) {
app_state.add_notification(format!("Failed to load sheet: {e}"));
} else {
app_state.start_editing();
}
} else {
app_state.start_editing();
}
}
KeyCode::Char('h') => {
app_state.g_pressed = false;
app_state.move_cursor(0, -1);
}
KeyCode::Char('j') => {
app_state.g_pressed = false;
app_state.move_cursor(1, 0);
}
KeyCode::Char('k') => {
app_state.g_pressed = false;
app_state.move_cursor(-1, 0);
}
KeyCode::Char('l') => {
app_state.g_pressed = false;
app_state.move_cursor(0, 1);
}
KeyCode::Char('u') => {
app_state.g_pressed = false;
if let Err(e) = app_state.undo() {
app_state.add_notification(format!("Undo failed: {e}"));
}
}
KeyCode::Char('=' | '+') => {
app_state.g_pressed = false;
app_state.adjust_info_panel_height(1);
}
KeyCode::Char('-') => {
app_state.g_pressed = false;
app_state.adjust_info_panel_height(-1);
}
KeyCode::Char('[') => {
app_state.g_pressed = false;
if let Err(e) = app_state.prev_sheet() {
app_state.add_notification(format!("Failed to switch to previous sheet: {e}"));
}
}
KeyCode::Char(']') => {
app_state.g_pressed = false;
if let Err(e) = app_state.next_sheet() {
app_state.add_notification(format!("Failed to switch to next sheet: {e}"));
}
}
KeyCode::Char('g') => {
if app_state.g_pressed {
app_state.jump_to_first_row();
app_state.g_pressed = false;
} else {
app_state.g_pressed = true;
}
}
KeyCode::Char('G') => {
app_state.g_pressed = false;
app_state.jump_to_last_row();
}
KeyCode::Char('0') => {
app_state.g_pressed = false;
app_state.jump_to_first_column();
}
KeyCode::Char('^') => {
app_state.g_pressed = false;
app_state.jump_to_first_non_empty_column();
}
KeyCode::Char('$') => {
app_state.g_pressed = false;
app_state.jump_to_last_column();
}
KeyCode::Char('y') => {
app_state.g_pressed = false;
app_state.copy_cell();
}
KeyCode::Char('d') => {
app_state.g_pressed = false;
if let Err(e) = app_state.cut_cell() {
app_state.add_notification(format!("Cut failed: {e}"));
}
}
KeyCode::Char('p') => {
app_state.g_pressed = false;
if let Err(e) = app_state.paste_cell() {
app_state.add_notification(format!("Paste failed: {e}"));
}
}
KeyCode::Char(':') => {
app_state.g_pressed = false;
app_state.start_command_mode();
}
KeyCode::Char('/') => {
app_state.g_pressed = false;
app_state.start_search_forward();
}
KeyCode::Char('?') => {
app_state.g_pressed = false;
app_state.start_search_backward();
}
KeyCode::Char('n') => {
app_state.g_pressed = false;
if !app_state.search_results.is_empty() {
app_state.jump_to_next_search_result();
} else if !app_state.search_query.is_empty() {
app_state.search_results = app_state.find_all_matches(&app_state.search_query);
if !app_state.search_results.is_empty() {
app_state.jump_to_next_search_result();
}
}
}
KeyCode::Char('N') => {
app_state.g_pressed = false;
if !app_state.search_results.is_empty() {
app_state.jump_to_prev_search_result();
} else if !app_state.search_query.is_empty() {
app_state.search_results = app_state.find_all_matches(&app_state.search_query);
if !app_state.search_results.is_empty() {
app_state.jump_to_prev_search_result();
}
}
}
KeyCode::Left => {
app_state.g_pressed = false;
app_state.move_cursor(0, -1);
}
KeyCode::Right => {
app_state.g_pressed = false;
app_state.move_cursor(0, 1);
}
KeyCode::Up => {
app_state.g_pressed = false;
app_state.move_cursor(-1, 0);
}
KeyCode::Down => {
app_state.g_pressed = false;
app_state.move_cursor(1, 0);
}
_ => {
app_state.g_pressed = false;
}
}
}
fn handle_editing_mode(app_state: &mut AppState, key: KeyEvent) {
let input = Input {
key: key_code_to_tui_key(key.code),
ctrl: key.modifiers.contains(KeyModifiers::CONTROL),
alt: key.modifiers.contains(KeyModifiers::ALT),
shift: key.modifiers.contains(KeyModifiers::SHIFT),
};
if let Err(e) = app_state.handle_vim_input(input) {
app_state.add_notification(format!("Vim input error: {e}"));
}
}
fn handle_search_mode(app_state: &mut AppState, key_code: KeyCode) {
match key_code {
KeyCode::Enter => app_state.execute_search(),
KeyCode::Esc => {
app_state.input_mode = InputMode::Normal;
app_state.input_buffer = String::new();
app_state.text_area = TextArea::default();
}
_ => {
let input = Input {
key: key_code_to_tui_key(key_code),
ctrl: false,
alt: false,
shift: false,
};
app_state.text_area.input(input);
}
}
}
fn key_code_to_tui_key(key_code: KeyCode) -> Key {
match key_code {
KeyCode::Backspace => Key::Backspace,
KeyCode::Enter => Key::Enter,
KeyCode::Left => Key::Left,
KeyCode::Right => Key::Right,
KeyCode::Up => Key::Up,
KeyCode::Down => Key::Down,
KeyCode::Home => Key::Home,
KeyCode::End => Key::End,
KeyCode::PageUp => Key::PageUp,
KeyCode::PageDown => Key::PageDown,
KeyCode::Tab => Key::Tab,
KeyCode::Delete => Key::Delete,
KeyCode::BackTab | KeyCode::Insert => Key::Null,
KeyCode::Esc => Key::Esc,
KeyCode::Char(c) => Key::Char(c),
KeyCode::F(n) => Key::F(n),
_ => Key::Null,
}
}
fn handle_lazy_loading_mode(app_state: &mut AppState, key_code: KeyCode) {
match key_code {
KeyCode::Enter => {
let index = app_state.workbook.get_current_sheet_index();
let sheet_name = app_state.workbook.get_current_sheet_name();
if let Err(e) = app_state.workbook.ensure_sheet_loaded(index, &sheet_name) {
app_state.add_notification(format!("Failed to load sheet: {e}"));
}
app_state.input_mode = InputMode::Normal;
}
KeyCode::Char('[') => {
let current_index = app_state.workbook.get_current_sheet_index();
if current_index == 0 {
app_state.add_notification("Already at the first sheet".to_string());
} else {
if let Err(e) = app_state.switch_sheet_by_index(current_index - 1) {
app_state.add_notification(format!("Failed to switch to previous sheet: {e}"));
}
}
}
KeyCode::Char(']') => {
let current_index = app_state.workbook.get_current_sheet_index();
let sheet_count = app_state.workbook.get_sheet_names().len();
if current_index >= sheet_count - 1 {
app_state.add_notification("Already at the last sheet".to_string());
} else {
if let Err(e) = app_state.switch_sheet_by_index(current_index + 1) {
app_state.add_notification(format!("Failed to switch to next sheet: {e}"));
}
}
}
KeyCode::Char(':') => {
app_state.start_command_in_lazy_loading_mode();
}
_ => {
app_state.add_notification(
"Press Enter to load the sheet data, or use [ and ] to switch sheets".to_string(),
);
}
}
}
fn handle_help_mode(app_state: &mut AppState, key_code: KeyCode) {
let line_count = app_state.help_text.lines().count();
let visible_lines = app_state.help_visible_lines;
let max_scroll = line_count.saturating_sub(visible_lines).max(0);
match key_code {
KeyCode::Enter | KeyCode::Esc => {
app_state.input_mode = InputMode::Normal;
}
KeyCode::Char('j') | KeyCode::Down => {
app_state.help_scroll = (app_state.help_scroll + 1).min(max_scroll);
}
KeyCode::Char('k') | KeyCode::Up => {
app_state.help_scroll = app_state.help_scroll.saturating_sub(1);
}
KeyCode::Home => {
app_state.help_scroll = 0;
}
KeyCode::End => {
app_state.help_scroll = max_scroll;
}
_ => {}
}
}