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