Skip to content

Commit 302cecd

Browse files
author
kiran-garre
committed
feat: Add clear-finished subcommand for /todos
Also: - Formatted - Added command and subcommand descriptions - Added newlines and things for cleanliness
1 parent c000ffb commit 302cecd

File tree

4 files changed

+127
-84
lines changed

4 files changed

+127
-84
lines changed

crates/chat-cli/src/cli/chat/cli/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub enum SlashCommand {
8484
Persist(PersistSubcommand),
8585
// #[command(flatten)]
8686
// Root(RootSubcommand),
87+
/// View, manage, and resume to-do lists
8788
#[command(subcommand)]
8889
Todos(TodoSubcommand),
8990
}

crates/chat-cli/src/cli/chat/cli/todos.rs

Lines changed: 120 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ use crate::os::Os;
1717

1818
#[derive(Debug, PartialEq, Subcommand)]
1919
pub enum TodoSubcommand {
20-
// Show all tracked to-do lists
20+
/// Show all tracked to-do lists
2121
Show,
2222

23-
// Clear completed to-do lists
23+
/// Delete all completed to-do lists
2424
ClearFinished,
25-
26-
// Resume a selected to-do list
25+
26+
/// Resume a selected to-do list
2727
Resume,
28-
29-
// View a to-do list
28+
29+
/// View a to-do list
3030
View,
3131
}
3232

@@ -61,7 +61,7 @@ impl TodoSubcommand {
6161
Self::Show => match Self::get_descriptions_and_statuses(os) {
6262
Ok(entries) => {
6363
if entries.is_empty() {
64-
execute!(session.stderr, style::Print("No to-do lists to show"),)?;
64+
execute!(session.stderr, style::Print("No to-do lists to show\n"),)?;
6565
}
6666
for e in entries {
6767
execute!(session.stderr, style::Print(e), style::Print("\n"),)?;
@@ -70,16 +70,46 @@ impl TodoSubcommand {
7070
Err(_) => return Err(ChatError::Custom("Could not show to-do lists".into())),
7171
},
7272
Self::ClearFinished => {
73-
73+
let entries = match os.database.get_all_todos() {
74+
Ok(e) => e,
75+
Err(_) => return Err(ChatError::Custom("Could not get all to-do lists".into())),
76+
};
77+
let mut cleared_one = false;
78+
for (id, value) in entries.iter() {
79+
let temp_struct = match value.as_str() {
80+
Some(s) => match serde_json::from_str::<TodoState>(s) {
81+
Ok(state) => state,
82+
Err(_) => continue,
83+
},
84+
None => continue,
85+
};
86+
if temp_struct.completed.iter().all(|b| *b) {
87+
match os.database.delete_todo(id) {
88+
Ok(_) => cleared_one = true,
89+
Err(_) => return Err(ChatError::Custom("Could not delete to-do list".into())),
90+
};
91+
}
92+
}
93+
if cleared_one {
94+
execute!(
95+
session.stderr,
96+
style::Print("✔ Cleared finished to-do lists!\n".green())
97+
)?;
98+
} else {
99+
execute!(
100+
session.stderr,
101+
style::Print("No finished to-do lists to clear!\n".green())
102+
)?;
103+
}
74104
},
75105
Self::Resume => {
76106
match Self::get_descriptions_and_statuses(os) {
77107
Ok(entries) => {
78108
if entries.is_empty() {
79-
execute!(session.stderr, style::Print("No to-do lists to show"),)?;
109+
execute!(session.stderr, style::Print("No to-do lists to show\n"),)?;
80110
} else {
81111
let selection = FuzzySelect::new()
82-
.with_prompt("Select task to resume:")
112+
.with_prompt("Select a to-do list to resume:")
83113
.items(&entries)
84114
.report(false)
85115
.interact_opt()
@@ -100,41 +130,47 @@ impl TodoSubcommand {
100130
Err(_) => return Err(ChatError::Custom("Could not show to-do lists".into())),
101131
};
102132
},
103-
Self::View => {
104-
match Self::get_descriptions_and_statuses(os) {
105-
Ok(entries) => {
106-
if entries.is_empty() {
107-
execute!(session.stderr, style::Print("No to-do lists to view"))?;
108-
} else {
109-
let selection = FuzzySelect::new()
110-
.with_prompt("Select task to view:")
111-
.items(&entries)
112-
.report(false)
113-
.interact_opt()
114-
.unwrap_or(None);
115-
116-
if let Some(index) = selection {
117-
if index < entries.len() {
118-
let list = match TodoState::load(os, &entries[index].id) {
119-
Ok(list) => list,
120-
Err(_) => {
121-
return Err(ChatError::Custom("Could not load requested to-do list".into()));
122-
}
123-
};
124-
match list.display_list(&mut session.stderr) {
125-
Ok(_) => {},
126-
Err(_) => {
127-
return Err(ChatError::Custom("Could not display requested to-do list".into()));
128-
}
129-
};
130-
execute!(session.stderr, style::Print("\n"),)?;
131-
}
133+
Self::View => match Self::get_descriptions_and_statuses(os) {
134+
Ok(entries) => {
135+
if entries.is_empty() {
136+
execute!(session.stderr, style::Print("No to-do lists to view\n"))?;
137+
} else {
138+
let selection = FuzzySelect::new()
139+
.with_prompt("Select a to-do list to view:")
140+
.items(&entries)
141+
.report(false)
142+
.interact_opt()
143+
.unwrap_or(None);
144+
145+
if let Some(index) = selection {
146+
if index < entries.len() {
147+
let list = match TodoState::load(os, &entries[index].id) {
148+
Ok(list) => list,
149+
Err(_) => {
150+
return Err(ChatError::Custom("Could not load requested to-do list".into()));
151+
},
152+
};
153+
execute!(
154+
session.stderr,
155+
style::Print(format!(
156+
"{} {}\n",
157+
"Viewing:".magenta(),
158+
entries[index].description.clone()
159+
))
160+
)?;
161+
match list.display_list(&mut session.stderr) {
162+
Ok(_) => {},
163+
Err(_) => {
164+
return Err(ChatError::Custom("Could not display requested to-do list".into()));
165+
},
166+
};
167+
execute!(session.stderr, style::Print("\n"),)?;
132168
}
133169
}
134-
},
135-
Err(_) => return Err(ChatError::Custom("Could not show to-do lists".into())),
136-
}
137-
}
170+
}
171+
},
172+
Err(_) => return Err(ChatError::Custom("Could not show to-do lists".into())),
173+
},
138174
}
139175
Ok(ChatState::PromptUser {
140176
skip_printing_tools: true,
@@ -155,7 +191,7 @@ impl TodoSubcommand {
155191
};
156192
// For some reason this doesn't work
157193
// Has to do with the Value::String wrapping in os.database.all_entries() rather than
158-
// Value::from_str()
194+
// Value::from_str()
159195
// let temp_struct = match
160196
// serde_json::from_value::<TodoState>(value.clone()) { Ok(state) => state,
161197
// Err(_) => continue,
@@ -164,48 +200,50 @@ impl TodoSubcommand {
164200
out.push(TodoDisplayEntry {
165201
num_completed: temp_struct.completed.iter().filter(|b| **b).count(),
166202
num_tasks: temp_struct.completed.len(),
167-
description: prewrap(&temp_struct.task_description),
203+
description: temp_struct.task_description,
168204
id: id.clone(),
169205
});
170206
}
171207
Ok(out)
172208
}
173209
}
174210

175-
const MAX_LINE_LENGTH: usize = 80;
176-
177-
// FIX: Hacky workaround for cleanly wrapping lines
178-
/// Insert newlines every n characters, not within a word and not at the end.
179-
///
180-
/// Generated by Q
181-
fn prewrap(text: &str) -> String {
182-
if text.is_empty() || MAX_LINE_LENGTH == 0 {
183-
return text.to_string();
184-
}
185-
186-
let mut result = String::new();
187-
let mut current_line_length = 0;
188-
let words: Vec<&str> = text.split_whitespace().collect();
189-
190-
for word in words.iter() {
191-
let word_length = word.len();
192-
193-
// If adding this word would exceed the line length and we're not at the start of a line
194-
if current_line_length > 0 && current_line_length + 1 + word_length > MAX_LINE_LENGTH {
195-
result.push('\n');
196-
result.push_str(&" ".repeat("> ".len()));
197-
current_line_length = 0;
198-
}
199-
200-
// Add space before word if not at start of line
201-
if current_line_length > 0 {
202-
result.push(' ');
203-
current_line_length += 1;
204-
}
205-
206-
result.push_str(word);
207-
current_line_length += word_length;
208-
}
209-
210-
result
211-
}
211+
// const MAX_LINE_LENGTH: usize = 80;
212+
213+
// // FIX: Hacky workaround for cleanly wrapping lines
214+
// /// Insert newlines every n characters, not within a word and not at the end.
215+
// /// This function is very hacky and barely works (do not use).
216+
// ///
217+
// /// Generated by Q
218+
// ///
219+
// fn _prewrap(text: &str) -> String {
220+
// if text.is_empty() || MAX_LINE_LENGTH == 0 {
221+
// return text.to_string();
222+
// }
223+
224+
// let mut result = String::new();
225+
// let mut current_line_length = 0;
226+
// let words: Vec<&str> = text.split_whitespace().collect();
227+
228+
// for word in words.iter() {
229+
// let word_length = word.len();
230+
231+
// // If adding this word would exceed the line length and we're not at the start of a line
232+
// if current_line_length > 0 && current_line_length + 1 + word_length > MAX_LINE_LENGTH {
233+
// result.push('\n');
234+
// result.push_str(&" ".repeat("> ".len()));
235+
// current_line_length = 0;
236+
// }
237+
238+
// // Add space before word if not at start of line
239+
// if current_line_length > 0 {
240+
// result.push(' ');
241+
// current_line_length += 1;
242+
// }
243+
244+
// result.push_str(word);
245+
// current_line_length += word_length;
246+
// }
247+
248+
// result
249+
// }

crates/chat-cli/src/cli/chat/tools/todo.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ impl TodoState {
102102
queue!(
103103
output,
104104
style::SetForegroundColor(style::Color::Reset),
105-
style::Print(format!(" ☐ {}", task)),
105+
style::Print(format!(" ☐ {task}")),
106106
)?;
107107
}
108108
Ok(())
@@ -124,7 +124,7 @@ impl TodoState {
124124
.expect("Time went backwards")
125125
.as_millis();
126126

127-
format!("{}", timestamp)
127+
format!("{timestamp}")
128128
}
129129
}
130130

crates/chat-cli/src/database/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,10 @@ impl Database {
389389
self.all_entries(Table::Todos)
390390
}
391391

392+
pub fn delete_todo(&self, id: &str) -> Result<(), DatabaseError> {
393+
self.delete_entry(Table::Todos, id)
394+
}
395+
392396
// Private functions. Do not expose.
393397

394398
fn migrate(self) -> Result<Self, DatabaseError> {

0 commit comments

Comments
 (0)