j_cli/command/todo/
mod.rs1pub mod app;
2pub mod ui;
3
4use crate::config::YamlConfig;
5use crate::{error, info, usage};
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 match content[0].as_str() {
29 "list" => handle_todo_list(),
30 "add" => {
31 let text = content[1..].join(" ");
32 let text = text.trim().trim_matches('"').to_string();
33 if text.is_empty() {
34 error!("⚠️ 内容为空,无法添加待办");
35 return;
36 }
37 quick_add_todo(&text);
38 }
39 _ => {
40 usage!("j todo | j todo list | j todo add <内容>");
41 }
42 }
43}
44
45fn quick_add_todo(text: &str) {
47 let mut todo_list = load_todo_list();
48 todo_list.items.push(TodoItem {
49 content: text.to_string(),
50 done: false,
51 created_at: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
52 done_at: None,
53 });
54
55 if save_todo_list(&todo_list) {
56 info!("✅ 已添加待办: {}", text);
57 let undone = todo_list.items.iter().filter(|i| !i.done).count();
58 info!("📋 当前未完成待办: {} 条", undone);
59 }
60}
61
62fn handle_todo_list() {
64 let todo_list = load_todo_list();
65
66 if todo_list.items.is_empty() {
67 info!("📋 暂无待办");
68 return;
69 }
70
71 let total = todo_list.items.len();
72 let done_count = todo_list.items.iter().filter(|i| i.done).count();
73 let undone_count = total - done_count;
74
75 let mut md = format!(
76 "## 待办备忘录 — 共 {} 条 | ✅ {} | ⬜ {}\n\n",
77 total, done_count, undone_count
78 );
79
80 for item in &todo_list.items {
81 if item.done {
82 md.push_str(&format!("- [x] {}\n", item.content));
83 } else {
84 md.push_str(&format!("- [ ] {}\n", item.content));
85 }
86 }
87
88 crate::md!("{}", md);
89}
90
91fn run_todo_tui(config: &mut YamlConfig) {
93 match run_todo_tui_internal(config) {
94 Ok(_) => {}
95 Err(e) => {
96 error!("❌ TUI 启动失败: {}", e);
97 }
98 }
99}
100
101fn run_todo_tui_internal(config: &mut YamlConfig) -> io::Result<()> {
102 terminal::enable_raw_mode()?;
103 let mut stdout = io::stdout();
104 execute!(stdout, EnterAlternateScreen)?;
105
106 let backend = CrosstermBackend::new(stdout);
107 let mut terminal = Terminal::new(backend)?;
108
109 let mut app = TodoApp::new();
110 let mut last_input_len: usize = 0;
111 let mut prev_input_mode: Option<AppMode> = None;
113
114 loop {
115 terminal.draw(|f| draw_ui(f, &mut app))?;
116
117 let current_input_len = app.input.chars().count();
118 if current_input_len != last_input_len {
119 app.preview_scroll = 0;
120 last_input_len = current_input_len;
121 }
122
123 if event::poll(std::time::Duration::from_millis(100))? {
124 if let Event::Key(key) = event::read()? {
125 if (app.mode == AppMode::Adding || app.mode == AppMode::Editing)
127 && key.modifiers.contains(KeyModifiers::ALT)
128 {
129 match key.code {
130 KeyCode::Down => {
131 app.preview_scroll = app.preview_scroll.saturating_add(1);
132 continue;
133 }
134 KeyCode::Up => {
135 app.preview_scroll = app.preview_scroll.saturating_sub(1);
136 continue;
137 }
138 _ => {}
139 }
140 }
141
142 match app.mode {
143 AppMode::Normal => {
144 if handle_normal_mode(&mut app, key) {
145 break;
146 }
147 }
148 AppMode::Adding => {
149 prev_input_mode = Some(AppMode::Adding);
150 handle_input_mode(&mut app, key);
151 }
152 AppMode::Editing => {
153 prev_input_mode = Some(AppMode::Editing);
154 handle_input_mode(&mut app, key);
155 }
156 AppMode::ConfirmDelete => handle_confirm_delete(&mut app, key),
157 AppMode::ConfirmReport => handle_confirm_report(&mut app, key, config),
158 AppMode::ConfirmCancelInput => {
159 let prev = prev_input_mode.clone().unwrap_or(AppMode::Adding);
160 handle_confirm_cancel_input(&mut app, key, prev);
161 }
162 AppMode::Help => handle_help_mode(&mut app, key),
163 }
164 }
165 }
166 }
167
168 terminal::disable_raw_mode()?;
169 execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
170
171 Ok(())
172}