Project Overview
The Terminal TODO App is a command-line task manager built with Rust. It utilizes the tui
and crossterm
crates to create an interactive terminal user interface.
Key Features
- Dual list management (TODO and DONE lists)
- Interactive navigation
- Task editing and deletion
- Data persistence
- Vim-inspired keybindings
Technical Breakdown
1. State Management
The app's state is managed through a central App
struct:
struct App {
todos: Vec<String>,
done: Vec<String>,
input: String,
input_mode: InputMode,
todo_list_state: ListState,
done_list_state: ListState,
editing_index: Option<usize>,
}
This structure captures all the necessary data for the application, including the task lists, current input, and UI state.
2. User Input Handling
User input is processed in the main event loop:
fn run_app<B: tui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<()> {
loop {
terminal.draw(|f| ui(f, app))?;
if let Event::Key(key) = event::read()? {
match app.input_mode {
InputMode::Normal => match key.code {
// Handle normal mode inputs
},
InputMode::Editing => match key.code {
// Handle editing mode inputs
},
InputMode::EditingExisting => match key.code {
// Handle existing item editing inputs
},
}
}
}
}
This pattern allows for different behaviors based on the current input mode.
3. Rendering the UI
The terminal UI is rendered using the tui
crate:
fn ui<B: tui::backend::Backend>(f: &mut Frame<B>, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints([
Constraint::Length(3),
Constraint::Min(0),
Constraint::Length(3),
])
.split(f.size());
// Render TODO and DONE lists
let lists = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(chunks[1]);
let todo_items: Vec<ListItem> = app.todos.iter().map(|i| ListItem::new(i.as_ref())).collect();
let todo_list = List::new(todo_items)
.block(Block::default().borders(Borders::ALL).title("Todo"));
f.render_stateful_widget(todo_list, lists[0], &mut app.todo_list_state);
// Similar rendering for DONE list...
}
This function demonstrates how to create a layout and render widgets like lists and input fields.
4. Data Persistence
Task persistence is implemented using simple file I/O:
impl App {
pub fn save_to_file(&self, filename: &str) -> io::Result<()> {
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(filename)?;
writeln!(file, "[TODO]")?;
for item in &self.todos {
writeln!(file, "{}", item)?;
}
writeln!(file, "[DONE]")?;
for item in &self.done {
writeln!(file, "{}", item)?;
}
Ok(())
}
pub fn load_from_file(filename: &str) -> io::Result<Self> {
// Implementation of file loading...
}
}
This approach is straightforward to save and load the application state between sessions.
Top comments (0)