j_cli/command/todo/
mod.rs1pub mod app;
2pub mod ui;
3
4use crate::config::YamlConfig;
5use crate::{error, info};
6use app::{
7 AppMode, TodoApp, TodoItem, handle_confirm_cancel_input, handle_confirm_delete,
8 handle_confirm_report, handle_help_mode, handle_input_mode, handle_normal_mode, load_todo_list,
9 save_todo_list,
10};
11use chrono::Local;
12use crossterm::{
13 event::{self, Event, KeyCode, KeyModifiers},
14 execute,
15 terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
16};
17use ratatui::{Terminal, backend::CrosstermBackend};
18use std::io;
19use ui::draw_ui;
20
21pub fn handle_todo(content: &[String], config: &mut YamlConfig) {
23 if content.is_empty() {
24 run_todo_tui(config);
25 return;
26 }
27
28 let text = content.join(" ");
29 let text = text.trim().trim_matches('"').to_string();
30
31 if text.is_empty() {
32 error!("⚠️ 内容为空,无法添加待办");
33 return;
34 }
35
36 let mut list = load_todo_list();
37 list.items.push(TodoItem {
38 content: text.clone(),
39 done: false,
40 created_at: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
41 done_at: None,
42 });
43
44 if save_todo_list(&list) {
45 info!("✅ 已添加待办: {}", text);
46 let undone = list.items.iter().filter(|i| !i.done).count();
47 info!("📋 当前未完成待办: {} 条", undone);
48 }
49}
50
51fn run_todo_tui(config: &mut YamlConfig) {
53 match run_todo_tui_internal(config) {
54 Ok(_) => {}
55 Err(e) => {
56 error!("❌ TUI 启动失败: {}", e);
57 }
58 }
59}
60
61fn run_todo_tui_internal(config: &mut YamlConfig) -> io::Result<()> {
62 terminal::enable_raw_mode()?;
63 let mut stdout = io::stdout();
64 execute!(stdout, EnterAlternateScreen)?;
65
66 let backend = CrosstermBackend::new(stdout);
67 let mut terminal = Terminal::new(backend)?;
68
69 let mut app = TodoApp::new();
70 let mut last_input_len: usize = 0;
71 let mut prev_input_mode: Option<AppMode> = None;
73
74 loop {
75 terminal.draw(|f| draw_ui(f, &mut app))?;
76
77 let current_input_len = app.input.chars().count();
78 if current_input_len != last_input_len {
79 app.preview_scroll = 0;
80 last_input_len = current_input_len;
81 }
82
83 if event::poll(std::time::Duration::from_millis(100))? {
84 if let Event::Key(key) = event::read()? {
85 if (app.mode == AppMode::Adding || app.mode == AppMode::Editing)
87 && key.modifiers.contains(KeyModifiers::ALT)
88 {
89 match key.code {
90 KeyCode::Down => {
91 app.preview_scroll = app.preview_scroll.saturating_add(1);
92 continue;
93 }
94 KeyCode::Up => {
95 app.preview_scroll = app.preview_scroll.saturating_sub(1);
96 continue;
97 }
98 _ => {}
99 }
100 }
101
102 match app.mode {
103 AppMode::Normal => {
104 if handle_normal_mode(&mut app, key) {
105 break;
106 }
107 }
108 AppMode::Adding => {
109 prev_input_mode = Some(AppMode::Adding);
110 handle_input_mode(&mut app, key);
111 }
112 AppMode::Editing => {
113 prev_input_mode = Some(AppMode::Editing);
114 handle_input_mode(&mut app, key);
115 }
116 AppMode::ConfirmDelete => handle_confirm_delete(&mut app, key),
117 AppMode::ConfirmReport => handle_confirm_report(&mut app, key, config),
118 AppMode::ConfirmCancelInput => {
119 let prev = prev_input_mode.clone().unwrap_or(AppMode::Adding);
120 handle_confirm_cancel_input(&mut app, key, prev);
121 }
122 AppMode::Help => handle_help_mode(&mut app, key),
123 }
124 }
125 }
126 }
127
128 terminal::disable_raw_mode()?;
129 execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
130
131 Ok(())
132}