From d38d07a70d8ed06b5e37d23bbd6fee3d747786cc Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Mon, 11 Nov 2024 05:46:52 -0800 Subject: [PATCH 1/8] wip: remove description parsing --- src/parser.rs | 69 ++------------------------------------------------- 1 file changed, 2 insertions(+), 67 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 6935c55..996b640 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,16 +3,13 @@ use regex::Regex; #[derive(Debug, PartialEq)] pub struct ParsedTask { pub title: String, - pub description: Option, pub priority: Option, } pub fn parse_task_input(input: &str) -> ParsedTask { let priority_re = Regex::new(r"!(\d+)\s*").unwrap(); - let description_re = Regex::new(r"\{([^}]*)\}").unwrap(); let mut priority = None; - let mut description = None; // Priority for caps in priority_re.captures_iter(input) { @@ -25,17 +22,7 @@ pub fn parse_task_input(input: &str) -> ParsedTask { } } - // Description - let mut title = input.to_string(); - if let Some(caps) = description_re.captures(&title) { - if let Some(desc_match) = caps.get(1) { - description = Some(desc_match.as_str().trim().to_string()); - } - } - - title = description_re.replace_all(&title, "").to_string(); - - title = priority_re.replace_all(&title, "").to_string(); + let title = priority_re.replace_all(&input, "").to_string(); let title = Regex::new(r"\s+") .unwrap() @@ -43,65 +30,13 @@ pub fn parse_task_input(input: &str) -> ParsedTask { .trim() .to_string(); - ParsedTask { - title, - priority, - description, - } + ParsedTask { title, priority } } #[cfg(test)] mod tests { use super::*; - #[test] - fn test_parse_with_description() { - let input = "Implement feature X {This is the description of the task}"; - let expected = ParsedTask { - title: "Implement feature X".to_string(), - description: Some("This is the description of the task".to_string()), - priority: None, - }; - let result = parse_task_input(input); - assert_eq!(result, expected); - } - - #[test] - fn test_parse_with_description_and_priority() { - let input = "Fix bug in module !2 {Critical issue that needs immediate attention}"; - let expected = ParsedTask { - title: "Fix bug in module".to_string(), - description: Some("Critical issue that needs immediate attention".to_string()), - priority: Some(2), - }; - let result = parse_task_input(input); - assert_eq!(result, expected); - } - - #[test] - fn test_parse_with_description_and_priority_in_any_order() { - let input = "{Detailed description here} Update documentation !3"; - let expected = ParsedTask { - title: "Update documentation".to_string(), - description: Some("Detailed description here".to_string()), - priority: Some(3), - }; - let result = parse_task_input(input); - assert_eq!(result, expected); - } - - #[test] - fn test_parse_with_multiple_descriptions() { - let input = "Task title {First description} {Second description}"; - let expected = ParsedTask { - title: "Task title".to_string(), - description: Some("First description".to_string()), - priority: None, - }; - let result = parse_task_input(input); - assert_eq!(result, expected); - } - #[test] fn test_parse_with_priority_in_middle() { let input = "Update !4 software documentation"; From dae581919eb665432a9dd59afae88464a1e52594 Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Mon, 11 Nov 2024 07:02:57 -0800 Subject: [PATCH 2/8] feat: add description as seperate box during input --- src/app.rs | 105 ++++++++++++++++++++++----------- src/ui.rs | 170 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 185 insertions(+), 90 deletions(-) diff --git a/src/app.rs b/src/app.rs index 9a229e5..da9639e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,7 +10,9 @@ pub struct App { pub state: ListState, pub task_detail: Option, pub input_mode: InputMode, + pub active_input: ActiveInput, pub new_task_title: String, + pub new_task_description: String, pub page: usize, pub show_done_tasks: bool, } @@ -18,6 +20,12 @@ pub struct App { pub enum InputMode { Normal, Editing, + Insert, +} +#[derive(PartialEq)] +pub enum ActiveInput { + Title, + Description, } impl App { @@ -33,7 +41,9 @@ impl App { state, task_detail: None, input_mode: InputMode::Normal, + active_input: ActiveInput::Title, new_task_title: String::new(), + new_task_description: String::new(), page: 1, show_done_tasks: false, } @@ -139,6 +149,8 @@ impl App { KeyCode::Char('a') => { self.input_mode = InputMode::Editing; self.new_task_title.clear(); + self.new_task_description.clear(); + self.active_input = ActiveInput::Title; } KeyCode::Enter => { if let Err(err) = self.select_task(instance_url, api_key).await { @@ -147,47 +159,74 @@ impl App { } _ => {} }, - InputMode::Editing => { - match key.code { - KeyCode::Enter => { - if !self.new_task_title.trim().is_empty() { - let parsed_task = parse_task_input(&self.new_task_title); - if let Err(err) = create_new_task( - instance_url, - api_key, - &parsed_task.title, - parsed_task.description.as_deref(), - parsed_task.priority, - ) - .await - { - eprintln!("Error creating task: {}", err); - } else { - // Refresh the task list - if let Ok(all_tasks) = - fetch_tasks(instance_url, api_key, self.page).await - { - self.tasks = - all_tasks.into_iter().filter(|task| !task.done).collect(); - self.state.select(Some(0)); - } + InputMode::Editing => match key.code { + KeyCode::Char('i') => { + self.input_mode = InputMode::Insert; + } + KeyCode::Tab => { + self.active_input = match self.active_input { + ActiveInput::Title => ActiveInput::Description, + ActiveInput::Description => ActiveInput::Title, + }; + } + KeyCode::Enter => { + if self.new_task_title.trim().is_empty() { + eprintln!("Task title cannot be empty."); + } else { + let parsed_task = parse_task_input(&self.new_task_title); + + let description = if self.new_task_description.trim().is_empty() { + None + } else { + Some(self.new_task_description.as_str()) + }; + + if let Err(err) = create_new_task( + instance_url, + api_key, + &parsed_task.title, + description, + parsed_task.priority, + ) + .await + { + eprintln!("Error creating new task: {}", err); + } else { + if let Err(err) = self.refresh_tasks(instance_url, api_key).await { + eprintln!("Error fetching tasks: {}", err); } } + self.new_task_title.clear(); + self.new_task_description.clear(); self.input_mode = InputMode::Normal; } - KeyCode::Char(c) => { - self.new_task_title.push(c); // Handle character input - } - KeyCode::Backspace => { - self.new_task_title.pop(); // Handle backspace + } + KeyCode::Esc => { + self.new_task_title.clear(); + self.new_task_description.clear(); + self.input_mode = InputMode::Normal; + } + _ => {} + }, + InputMode::Insert => match key.code { + KeyCode::Char(c) => match self.active_input { + ActiveInput::Title => self.new_task_title.push(c), + ActiveInput::Description => self.new_task_description.push(c), + }, + KeyCode::Backspace => match self.active_input { + ActiveInput::Title => { + self.new_task_title.pop(); } - KeyCode::Esc => { - self.input_mode = InputMode::Normal; // Exit editing mode + ActiveInput::Description => { + self.new_task_description.pop(); } - _ => {} + }, + KeyCode::Esc => { + self.input_mode = InputMode::Editing; } - } + _ => {} + }, } Ok(false) } diff --git a/src/ui.rs b/src/ui.rs index d362d8e..de6c064 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,9 +1,9 @@ -use crate::app::{App, InputMode}; +use crate::app::{ActiveInput, App, InputMode}; use ansi_parser::{AnsiParser, Output}; -use crossterm::event::{self, Event as CEvent}; +use crossterm::event::{self, Event as CEvent, KeyCode}; use ratatui::{ backend::Backend, - layout::{Alignment, Constraint, Direction, Layout, Rect}, + layout::{Alignment, Constraint, Direction, Layout, Margin, Rect}, style::{Color, Modifier, Style}, text::{Line, Span, Text}, widgets::{Block, Borders, Clear, List, ListItem, Paragraph, Wrap}, @@ -12,29 +12,31 @@ use ratatui::{ use std::io; use std::time::Duration; +fn get_cursor_position(input: &str, area: Rect) -> (u16, u16) { + let lines: Vec<&str> = input.lines().collect(); + let last_line = lines.last().unwrap_or(&""); + let x = area.x + last_line.chars().count() as u16 + 1; + let y = area.y + lines.len() as u16 - 1 + 1; + (x, y) +} + fn centered_rect_absolute(width: u16, height: u16, r: Rect) -> Rect { let popup_layout = Layout::default() .direction(Direction::Vertical) - .constraints( - [ - Constraint::Length((r.height.saturating_sub(height)) / 2), - Constraint::Length(height), - Constraint::Length((r.height.saturating_sub(height) + 1) / 2), - ] - .as_ref(), - ) + .constraints([ + Constraint::Length((r.height.saturating_sub(height)) / 2u16), + Constraint::Length(height), + Constraint::Length((r.height.saturating_sub(height) + 1u16) / 2u16), + ]) .split(r); Layout::default() .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Length((r.width.saturating_sub(width)) / 2), - Constraint::Length(width), - Constraint::Length((r.width.saturating_sub(width) + 1) / 2), - ] - .as_ref(), - ) + .constraints([ + Constraint::Length((r.width.saturating_sub(width)) / 2u16), + Constraint::Length(width), + Constraint::Length((r.width.saturating_sub(width) + 1u16) / 2u16), + ]) .split(popup_layout[1])[1] } @@ -61,27 +63,24 @@ fn get_legend(input_mode: &InputMode) -> Text<'static> { InputMode::Normal => Text::from(Line::from(vec![ Span::styled(" q ", Style::default().fg(Color::Red)), Span::raw(": Quit "), - Span::styled(" j ", Style::default().fg(Color::Red)), - Span::raw(": Down "), - Span::styled(" k ", Style::default().fg(Color::Red)), - Span::raw(": Up "), - Span::styled(" n ", Style::default().fg(Color::Red)), - Span::raw(": Next Page "), - Span::styled(" p ", Style::default().fg(Color::Red)), - Span::raw(": Previous Page "), - Span::styled(" t ", Style::default().fg(Color::Red)), - Span::raw(": Toggle Done "), - Span::styled(" Enter ", Style::default().fg(Color::Red)), - Span::raw(": View Details "), + // ... other keys ... Span::styled(" a ", Style::default().fg(Color::Red)), Span::raw(": Add Task "), ])), InputMode::Editing => Text::from(Line::from(vec![ + Span::styled(" i ", Style::default().fg(Color::Red)), + Span::raw(": Insert "), + Span::styled(" Tab ", Style::default().fg(Color::Red)), + Span::raw(": Switch Input "), Span::styled(" Enter ", Style::default().fg(Color::Red)), Span::raw(": Submit "), Span::styled(" Esc ", Style::default().fg(Color::Red)), Span::raw(": Cancel "), ])), + InputMode::Insert => Text::from(Line::from(vec![ + Span::styled(" Esc ", Style::default().fg(Color::Red)), + Span::raw(": Exit Insert Mode "), + ])), } } @@ -99,7 +98,7 @@ pub async fn run_app( let chunks = Layout::default() .direction(Direction::Vertical) .margin(0) - .constraints([Constraint::Min(0), Constraint::Length(2)].as_ref()) + .constraints([Constraint::Min(0), Constraint::Length(2u16)]) .split(size); let body_chunk = chunks[0]; @@ -109,9 +108,7 @@ pub async fn run_app( InputMode::Normal => { let chunks = Layout::default() .direction(Direction::Horizontal) - .constraints( - [Constraint::Percentage(65), Constraint::Percentage(35)].as_ref(), - ) + .constraints([Constraint::Percentage(65), Constraint::Percentage(35)]) .split(body_chunk); let task_title = if app.show_done_tasks { @@ -157,10 +154,9 @@ pub async fn run_app( let detail_block = Block::default().borders(Borders::ALL).title("Task Details"); if let Some(ref detail) = app.task_detail { - // Initialize lines with 'static lifetime let mut lines: Vec> = Vec::new(); - // due_date + // Due date let due_date = match &detail.due_date { Some(date) if date != "0001-01-01T00:00:00Z" => date.clone(), _ => "No due date".to_string(), @@ -173,7 +169,7 @@ pub async fn run_app( Span::raw(due_date), ])); - // priority + // Priority let priority_str = match detail.priority { Some(p) => p.to_string(), None => "No priority".to_string(), @@ -186,7 +182,7 @@ pub async fn run_app( Span::raw(priority_str), ])); - // labels + // Labels lines.push(Line::from(vec![Span::styled( "Labels: ", Style::default().add_modifier(Modifier::BOLD), @@ -197,7 +193,7 @@ pub async fn run_app( let mut label_spans: Vec> = Vec::new(); for (i, label) in labels.iter().enumerate() { if i > 0 { - label_spans.push(Span::raw(" ".to_string())); + label_spans.push(Span::raw(" ")); } label_spans.push(Span::styled( format!(" {} ", label.title), @@ -211,7 +207,7 @@ pub async fn run_app( } } - // description + // Description lines.push(Line::from(vec![Span::styled( "Description: ", Style::default().add_modifier(Modifier::BOLD), @@ -242,36 +238,95 @@ pub async fn run_app( f.render_widget(paragraph, chunks[1]); } } - InputMode::Editing => { - let popup_width_percentage = 60; - let popup_width = (size.width * popup_width_percentage / 100).saturating_sub(2); + InputMode::Editing | InputMode::Insert => { + let popup_width_percentage = 60u16; + let popup_width = + (size.width * popup_width_percentage / 100u16).saturating_sub(2u16); - let lines_required = calculate_wrapped_lines(&app.new_task_title, popup_width); + // Calculate the required heights for the input boxes + let title_lines_required = + calculate_wrapped_lines(&app.new_task_title, popup_width); + let description_lines_required = + calculate_wrapped_lines(&app.new_task_description, popup_width); - let min_required_height = 1; + let title_height = std::cmp::max(title_lines_required as u16, 1u16); + let description_height = std::cmp::max(description_lines_required as u16, 2u16); // At least 2 lines tall - let required_height = std::cmp::max(lines_required as u16, min_required_height); + let total_height = title_height + description_height + 6u16; // +6 for borders and titles - let popup_height = required_height + 2; - - let max_popup_height = size.height - 2; - let popup_height = std::cmp::min(popup_height, max_popup_height); + let max_popup_height = size.height - 2u16; + let popup_height = std::cmp::min(total_height, max_popup_height); let popup_area = - centered_rect_absolute(popup_width + 2, popup_height, body_chunk); + centered_rect_absolute(popup_width + 2u16, popup_height, body_chunk); let popup_block = Block::default() - .title("Enter New Task (Press Enter to Submit)") + .title("Enter New Task (Press Enter to Submit, Tab to Switch)") .borders(Borders::ALL) .style(Style::default().fg(Color::Green)); - let input = Paragraph::new(app.new_task_title.as_str()) + // Split the popup area vertically for the two input boxes + let input_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(title_height + 2u16), // +2 for borders + Constraint::Length(description_height + 2u16), // +2 for borders + ]) + .split(popup_area.inner(Margin { + vertical: 1u16, + horizontal: 1u16, + })); // Adjust for popup_block borders + + // Title input box + let title_block = Block::default().borders(Borders::ALL).title("Title").style( + if app.active_input == ActiveInput::Title { + Style::default().fg(Color::Yellow) + } else { + Style::default() + }, + ); + + let title_paragraph = Paragraph::new(app.new_task_title.as_str()) .style(Style::default().fg(Color::White)) - .block(popup_block) + .block(title_block) + .wrap(Wrap { trim: false }); + + // Description input box + let description_block = Block::default() + .borders(Borders::ALL) + .title("Description") + .style(if app.active_input == ActiveInput::Description { + Style::default().fg(Color::Yellow) + } else { + Style::default() + }); + + let description_paragraph = Paragraph::new(app.new_task_description.as_str()) + .style(Style::default().fg(Color::White)) + .block(description_block) .wrap(Wrap { trim: false }); f.render_widget(Clear, popup_area); - f.render_widget(input, popup_area); + f.render_widget(popup_block, popup_area); + + f.render_widget(title_paragraph, input_chunks[0]); + f.render_widget(description_paragraph, input_chunks[1]); + + // Set cursor position + match app.active_input { + ActiveInput::Title => { + // Calculate cursor position in title input + let cursor_position = + get_cursor_position(&app.new_task_title, input_chunks[0]); + f.set_cursor(cursor_position.0, cursor_position.1); + } + ActiveInput::Description => { + // Calculate cursor position in description input + let cursor_position = + get_cursor_position(&app.new_task_description, input_chunks[1]); + f.set_cursor(cursor_position.0, cursor_position.1); + } + } } } @@ -300,7 +355,8 @@ fn calculate_wrapped_lines(text: &str, max_width: u16) -> usize { let mut line_count = 0; for line in text.lines() { let line_width = line.chars().count() as u16; - line_count += ((line_width + max_width - 1) / max_width) as usize; + let total_width = line_width + max_width - 1u16; + line_count += (total_width / max_width) as usize; } line_count } From 8769306e413d061a5dc0f638324669e61a05c80e Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Mon, 11 Nov 2024 07:08:37 -0800 Subject: [PATCH 3/8] docs: add license and metadata --- Cargo.toml | 4 ++++ LICENSE.md | 8 ++++++++ README.md | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 LICENSE.md diff --git a/Cargo.toml b/Cargo.toml index de142cf..078ebc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,10 @@ name = "vikunja-tui" version = "0.1.0" edition = "2021" +license = "MIT" +description = "A terminal application to manage tasks in vikunja" +repository = "https://github.com/mark-pitblado/vikunja-tui" +readme = "README.md" [dependencies] regex = "1" diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..4da5e57 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,8 @@ +Copyright 2024 Mark Pitblado + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README.md b/README.md index 5061140..345f317 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ api_key = "" - Add tasks - Title - Priority, via ![1-5] (vikunja quick add magic syntax) - - Description, via {} in the add task flow + - Description, via a seperate input box during task creation ## Roadmap From 8e219ba23737899cc965c9f0498c5bcd3c13525d Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Mon, 11 Nov 2024 07:13:48 -0800 Subject: [PATCH 4/8] fix: simplify error handling --- src/app.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index da9639e..74aa32a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -192,10 +192,8 @@ impl App { .await { eprintln!("Error creating new task: {}", err); - } else { - if let Err(err) = self.refresh_tasks(instance_url, api_key).await { - eprintln!("Error fetching tasks: {}", err); - } + } else if let Err(err) = self.refresh_tasks(instance_url, api_key).await { + eprintln!("Error fetching tasks: {}", err); } self.new_task_title.clear(); self.new_task_description.clear(); From 36903e31e30e4444f499ec06ce37e68c198e3bab Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Mon, 11 Nov 2024 07:44:56 -0800 Subject: [PATCH 5/8] fix: correct legend --- src/ui.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index de6c064..0efb19b 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,6 +1,6 @@ use crate::app::{ActiveInput, App, InputMode}; use ansi_parser::{AnsiParser, Output}; -use crossterm::event::{self, Event as CEvent, KeyCode}; +use crossterm::event::{self, Event as CEvent}; use ratatui::{ backend::Backend, layout::{Alignment, Constraint, Direction, Layout, Margin, Rect}, @@ -63,7 +63,18 @@ fn get_legend(input_mode: &InputMode) -> Text<'static> { InputMode::Normal => Text::from(Line::from(vec![ Span::styled(" q ", Style::default().fg(Color::Red)), Span::raw(": Quit "), - // ... other keys ... + Span::styled(" j ", Style::default().fg(Color::Red)), + Span::raw(": Down "), + Span::styled(" k ", Style::default().fg(Color::Red)), + Span::raw(": Up "), + Span::styled(" n ", Style::default().fg(Color::Red)), + Span::raw(": Next Page "), + Span::styled(" p ", Style::default().fg(Color::Red)), + Span::raw(": Previous Page "), + Span::styled(" t ", Style::default().fg(Color::Red)), + Span::raw(": Toggle Done "), + Span::styled(" Enter ", Style::default().fg(Color::Red)), + Span::raw(": View Details "), Span::styled(" a ", Style::default().fg(Color::Red)), Span::raw(": Add Task "), ])), From 3fecc310f4875ffed17411d3befbf511d10497d0 Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Mon, 11 Nov 2024 07:45:04 -0800 Subject: [PATCH 6/8] fix: remove unused model parameters --- src/models.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/models.rs b/src/models.rs index c9f242d..79cc560 100644 --- a/src/models.rs +++ b/src/models.rs @@ -11,9 +11,6 @@ pub struct Task { // TaskDetail struct with description #[derive(Deserialize, Debug)] pub struct TaskDetail { - pub id: u64, - pub title: String, - pub done: bool, pub due_date: Option, pub labels: Option>, pub priority: Option, @@ -23,6 +20,5 @@ pub struct TaskDetail { // Label struct #[derive(Deserialize, Debug)] pub struct Label { - pub id: u64, pub title: String, } From a6cb64eaac5232c0ee1c4c5a3176ade71f2a9421 Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Mon, 11 Nov 2024 07:46:36 -0800 Subject: [PATCH 7/8] fix: remove description from priority tests --- src/parser.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 996b640..75267d5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -43,7 +43,6 @@ mod tests { let expected = ParsedTask { title: "Update software documentation".to_string(), priority: Some(4), - description: None, }; let result = parse_task_input(input); assert_eq!(result, expected); @@ -55,7 +54,6 @@ mod tests { let expected = ParsedTask { title: "Fix bugs in the code".to_string(), priority: Some(2), - description: None, }; let result = parse_task_input(input); assert_eq!(result, expected); @@ -67,7 +65,6 @@ mod tests { let expected = ParsedTask { title: "Write tests for the parser".to_string(), priority: Some(3), - description: None, }; let result = parse_task_input(input); assert_eq!(result, expected); @@ -79,7 +76,6 @@ mod tests { let expected = ParsedTask { title: "Deploy to production".to_string(), priority: Some(5), - description: None, }; let result = parse_task_input(input); assert_eq!(result, expected); @@ -91,7 +87,6 @@ mod tests { let expected = ParsedTask { title: "Prepare presentation slides".to_string(), priority: Some(2), - description: None, }; let result = parse_task_input(input); assert_eq!(result, expected); @@ -103,7 +98,6 @@ mod tests { let expected = ParsedTask { title: "Organize team building event".to_string(), priority: Some(1), - description: None, }; let result = parse_task_input(input); assert_eq!(result, expected); @@ -115,7 +109,6 @@ mod tests { let expected = ParsedTask { title: "Check logs immediately".to_string(), priority: None, - description: None, }; let result = parse_task_input(input); assert_eq!(result, expected); From a4b568ae48c15e42da84037256cde457962fc9d5 Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Mon, 11 Nov 2024 07:53:51 -0800 Subject: [PATCH 8/8] fix: title reference --- src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index 75267d5..4a9c99c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -22,7 +22,7 @@ pub fn parse_task_input(input: &str) -> ParsedTask { } } - let title = priority_re.replace_all(&input, "").to_string(); + let title = priority_re.replace_all(input, "").into_owned(); let title = Regex::new(r"\s+") .unwrap()