use super::io::save_todo_list;
use super::state::TodoApp;
use super::types::AppMode;
use super::util::copy_to_clipboard;
use crate::command::report;
use crate::config::YamlConfig;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
pub fn handle_normal_mode(app: &mut TodoApp, key: KeyEvent) -> bool {
if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') {
return true;
}
match key.code {
KeyCode::Char('q') => {
if app.is_dirty() {
app.message = Some(
"⚠️ 有未保存的修改!请先 s 保存,或输入 q! 强制退出(丢弃修改)".to_string(),
);
app.quit_input = "q".to_string();
return false;
}
return true;
}
KeyCode::Esc => {
if app.is_dirty() {
app.message = Some(
"⚠️ 有未保存的修改!请先 s 保存,或输入 q! 强制退出(丢弃修改)".to_string(),
);
return false;
}
return true;
}
KeyCode::Char('!') => {
if app.quit_input == "q" {
return true;
}
app.quit_input.clear();
}
KeyCode::Char('n') | KeyCode::Down | KeyCode::Char('j') => app.move_down(),
KeyCode::Char('N') | KeyCode::Up | KeyCode::Char('k') => app.move_up(),
KeyCode::Char(' ') | KeyCode::Enter => app.toggle_done(),
KeyCode::Char('a') => {
app.mode = AppMode::Adding;
app.input.clear();
app.cursor_pos = 0;
app.message = None;
let count = app.filtered_indices().len();
app.state.select(Some(count));
}
KeyCode::Char('e') => {
if let Some(real_idx) = app.selected_real_index() {
app.input = app.list.items[real_idx].content.clone();
app.cursor_pos = app.input.chars().count();
app.edit_index = Some(real_idx);
app.mode = AppMode::Editing;
app.message = None;
}
}
KeyCode::Char('y') => {
if let Some(real_idx) = app.selected_real_index() {
let content = app.list.items[real_idx].content.clone();
if copy_to_clipboard(&content) {
app.message = Some(format!("📋 已复制到剪切板: {}", content));
} else {
app.message = Some("✖️ 复制到剪切板失败".to_string());
}
}
}
KeyCode::Char('d') if app.selected_real_index().is_some() => {
app.mode = AppMode::ConfirmDelete;
}
KeyCode::Char('f') => app.toggle_filter(),
KeyCode::Char('s') => app.save(),
KeyCode::Char('K') => app.move_item_up(),
KeyCode::Char('J') => app.move_item_down(),
KeyCode::Char('?') => {
app.mode = AppMode::Help;
}
KeyCode::Char('/') => {
app.mode = AppMode::CommandPopup;
app.cmd_popup_filter.clear();
app.cmd_popup_selected = 0;
}
_ => {}
}
if key.code != KeyCode::Char('q') && key.code != KeyCode::Char('!') {
app.quit_input.clear();
}
false
}
pub fn handle_input_mode(app: &mut TodoApp, key: KeyEvent) {
let char_count = app.input.chars().count();
match key.code {
KeyCode::Enter => {
if app.mode == AppMode::Adding {
app.add_item();
} else {
app.confirm_edit();
}
}
KeyCode::Esc => {
let has_changes = if app.mode == AppMode::Adding {
!app.input.trim().is_empty()
} else if app.mode == AppMode::Editing {
if let Some(idx) = app.edit_index {
if idx < app.list.items.len() {
app.input.trim() != app.list.items[idx].content.trim()
} else {
!app.input.trim().is_empty()
}
} else {
!app.input.trim().is_empty()
}
} else {
false
};
if has_changes {
app.mode = AppMode::ConfirmCancelInput;
app.message = Some(
"⚠️ 有未保存的内容,是否保存?(Enter/y 保存 / n 放弃 / 其他键继续编辑)"
.to_string(),
);
} else {
app.mode = AppMode::Normal;
app.input.clear();
app.cursor_pos = 0;
app.edit_index = None;
app.message = Some("已取消".to_string());
}
}
KeyCode::Left if app.cursor_pos > 0 => {
app.cursor_pos -= 1;
}
KeyCode::Right if app.cursor_pos < char_count => {
app.cursor_pos += 1;
}
KeyCode::Home => {
app.cursor_pos = 0;
}
KeyCode::End => {
app.cursor_pos = char_count;
}
KeyCode::Backspace if app.cursor_pos > 0 => {
let start = app
.input
.char_indices()
.nth(app.cursor_pos - 1)
.map(|(i, _)| i)
.unwrap_or(0);
let end = app
.input
.char_indices()
.nth(app.cursor_pos)
.map(|(i, _)| i)
.unwrap_or(app.input.len());
app.input.drain(start..end);
app.cursor_pos -= 1;
}
KeyCode::Delete if app.cursor_pos < char_count => {
let start = app
.input
.char_indices()
.nth(app.cursor_pos)
.map(|(i, _)| i)
.unwrap_or(app.input.len());
let end = app
.input
.char_indices()
.nth(app.cursor_pos + 1)
.map(|(i, _)| i)
.unwrap_or(app.input.len());
app.input.drain(start..end);
}
KeyCode::Char(c) => {
let byte_idx = app
.input
.char_indices()
.nth(app.cursor_pos)
.map(|(i, _)| i)
.unwrap_or(app.input.len());
app.input.insert(byte_idx, c);
app.cursor_pos += 1;
}
_ => {}
}
}
pub fn handle_confirm_delete(app: &mut TodoApp, key: KeyEvent) {
match key.code {
KeyCode::Char('y') | KeyCode::Char('Y') => {
app.delete_selected();
}
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
app.mode = AppMode::Normal;
app.message = Some("已取消删除".to_string());
}
_ => {}
}
}
pub fn handle_help_mode(app: &mut TodoApp, _key: KeyEvent) {
app.mode = AppMode::Normal;
app.message = None;
}
pub fn handle_confirm_cancel_input(app: &mut TodoApp, key: KeyEvent, prev_mode: AppMode) {
match key.code {
KeyCode::Enter | KeyCode::Char('y') | KeyCode::Char('Y') => {
if prev_mode == AppMode::Adding {
app.add_item();
} else {
app.confirm_edit();
}
}
KeyCode::Char('n') | KeyCode::Char('N') => {
app.mode = AppMode::Normal;
app.input.clear();
app.cursor_pos = 0;
app.edit_index = None;
app.message = Some("已放弃".to_string());
}
KeyCode::Esc => {
app.mode = AppMode::Normal;
app.input.clear();
app.cursor_pos = 0;
app.edit_index = None;
app.message = Some("已放弃".to_string());
}
_ => {
app.mode = prev_mode;
app.message = None;
}
}
}
pub fn handle_command_popup_mode(app: &mut TodoApp, key: KeyEvent) {
let items = app.filtered_cmd_items();
match key.code {
KeyCode::Esc => {
app.mode = AppMode::Normal;
}
KeyCode::Up | KeyCode::Char('k') if !items.is_empty() => {
if app.cmd_popup_selected > 0 {
app.cmd_popup_selected -= 1;
} else {
app.cmd_popup_selected = items.len() - 1;
}
}
KeyCode::Down | KeyCode::Char('j') if !items.is_empty() => {
if app.cmd_popup_selected < items.len() - 1 {
app.cmd_popup_selected += 1;
} else {
app.cmd_popup_selected = 0;
}
}
KeyCode::Backspace => {
if app.cmd_popup_filter.pop().is_none() {
app.mode = AppMode::Normal;
} else {
app.cmd_popup_selected = 0;
}
}
KeyCode::Enter => {
let selected = app.cmd_popup_selected.min(items.len().saturating_sub(1));
if let Some((_, key, _)) = items.get(selected) {
match *key {
"toggle" => {
app.toggle_done();
return; }
"edit" => {
if let Some(real_idx) = app.selected_real_index() {
app.input = app.list.items[real_idx].content.clone();
app.cursor_pos = app.input.chars().count();
app.edit_index = Some(real_idx);
app.mode = AppMode::Editing;
app.message = None;
}
return;
}
"add" => {
app.mode = AppMode::Adding;
app.input.clear();
app.cursor_pos = 0;
app.message = None;
let count = app.filtered_indices().len();
app.state.select(Some(count));
return;
}
"delete" => {
if app.selected_real_index().is_some() {
app.mode = AppMode::ConfirmDelete;
} else {
app.mode = AppMode::Normal;
}
return;
}
"copy" => {
if let Some(real_idx) = app.selected_real_index() {
let content = app.list.items[real_idx].content.clone();
if copy_to_clipboard(&content) {
app.message = Some(format!("📋 已复制到剪切板: {}", content));
} else {
app.message = Some("✖️ 复制到剪切板失败".to_string());
}
}
}
"filter" => {
app.toggle_filter();
}
"moveup" => {
app.move_item_up();
}
"movedown" => {
app.move_item_down();
}
"save" => {
app.save();
}
"quit" => {
if app.is_dirty() {
app.message = Some(
"⚠️ 有未保存的修改!请先 s 保存,或输入 q! 强制退出(丢弃修改)"
.to_string(),
);
app.quit_input = "q".to_string();
} else {
app.mode = AppMode::Normal;
app.message = Some("按 q 或 Esc 退出".to_string());
}
return;
}
"help" => {
app.mode = AppMode::Help;
return;
}
_ => {}
}
}
app.mode = AppMode::Normal;
}
KeyCode::Char(c) => {
app.cmd_popup_filter.push(c);
app.cmd_popup_selected = 0;
}
_ => {}
}
}
pub fn handle_confirm_report(app: &mut TodoApp, key: KeyEvent, config: &mut YamlConfig) {
match key.code {
KeyCode::Enter | KeyCode::Char('y') | KeyCode::Char('Y') => {
if let Some(content) = app.report_pending_content.take() {
let content_with_done = format!("{} [Done]", content);
let write_ok = report::write_to_report(&content_with_done, config);
if app.is_dirty() && save_todo_list(&app.list) {
app.snapshot = app.list.clone();
}
if write_ok {
app.message = Some("☑️ 已标记为完成,已写入日报并保存".to_string());
} else {
app.message = Some("☑️ 已标记为完成,但写入日报失败".to_string());
}
}
app.mode = AppMode::Normal;
}
_ => {
app.report_pending_content = None;
app.message = Some("☑️ 已标记为完成".to_string());
app.mode = AppMode::Normal;
}
}
}