Add docs to everything public. Rename some methods.Remove unused deps
This commit is contained in:
		
							parent
							
								
									9a779b1abb
								
							
						
					
					
						commit
						4beda4fae7
					
				
							
								
								
									
										68
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										68
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -301,13 +301,7 @@ dependencies = [
 | 
				
			|||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "itoa"
 | 
					name = "kanban_tui"
 | 
				
			||||||
version = "1.0.6"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "kanban-tui"
 | 
					 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "anyhow",
 | 
					 "anyhow",
 | 
				
			||||||
@ -316,9 +310,6 @@ dependencies = [
 | 
				
			|||||||
 "int-enum",
 | 
					 "int-enum",
 | 
				
			||||||
 "ratatui",
 | 
					 "ratatui",
 | 
				
			||||||
 "rusqlite",
 | 
					 "rusqlite",
 | 
				
			||||||
 "serde",
 | 
					 | 
				
			||||||
 "serde_json",
 | 
					 | 
				
			||||||
 "thiserror",
 | 
					 | 
				
			||||||
 "tui-textarea",
 | 
					 "tui-textarea",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -492,49 +483,12 @@ dependencies = [
 | 
				
			|||||||
 "windows-sys 0.48.0",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "ryu"
 | 
					 | 
				
			||||||
version = "1.0.13"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "scopeguard"
 | 
					name = "scopeguard"
 | 
				
			||||||
version = "1.1.0"
 | 
					version = "1.1.0"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 | 
					checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "serde"
 | 
					 | 
				
			||||||
version = "1.0.163"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "serde_derive",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "serde_derive"
 | 
					 | 
				
			||||||
version = "1.0.163"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "proc-macro2",
 | 
					 | 
				
			||||||
 "quote",
 | 
					 | 
				
			||||||
 "syn 2.0.18",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "serde_json"
 | 
					 | 
				
			||||||
version = "1.0.96"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "itoa",
 | 
					 | 
				
			||||||
 "ryu",
 | 
					 | 
				
			||||||
 "serde",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "signal-hook"
 | 
					name = "signal-hook"
 | 
				
			||||||
version = "0.3.15"
 | 
					version = "0.3.15"
 | 
				
			||||||
@ -599,26 +553,6 @@ dependencies = [
 | 
				
			|||||||
 "unicode-ident",
 | 
					 "unicode-ident",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "thiserror"
 | 
					 | 
				
			||||||
version = "1.0.40"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "thiserror-impl",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					 | 
				
			||||||
name = "thiserror-impl"
 | 
					 | 
				
			||||||
version = "1.0.40"
 | 
					 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					 | 
				
			||||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
 | 
					 | 
				
			||||||
dependencies = [
 | 
					 | 
				
			||||||
 "proc-macro2",
 | 
					 | 
				
			||||||
 "quote",
 | 
					 | 
				
			||||||
 "syn 2.0.18",
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "toml_datetime"
 | 
					name = "toml_datetime"
 | 
				
			||||||
version = "0.6.2"
 | 
					version = "0.6.2"
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "kanban-tui"
 | 
					name = "kanban_tui"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
edition = "2021"
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -9,10 +9,7 @@ edition = "2021"
 | 
				
			|||||||
tui = { package = "ratatui", version = "0.20.1" }
 | 
					tui = { package = "ratatui", version = "0.20.1" }
 | 
				
			||||||
tui-textarea = { version = "0.2.0", git = "https://github.com/rhysd/tui-textarea.git", features = ["ratatui-crossterm"], default-features=false }
 | 
					tui-textarea = { version = "0.2.0", git = "https://github.com/rhysd/tui-textarea.git", features = ["ratatui-crossterm"], default-features=false }
 | 
				
			||||||
crossterm = "0.26.1"
 | 
					crossterm = "0.26.1"
 | 
				
			||||||
serde = { version = "1.0.148" , features = [ "derive" ] }
 | 
					 | 
				
			||||||
serde_json = "1.0.89"
 | 
					 | 
				
			||||||
int-enum = "0.5.0"
 | 
					int-enum = "0.5.0"
 | 
				
			||||||
thiserror = "1"
 | 
					 | 
				
			||||||
anyhow = "1"
 | 
					anyhow = "1"
 | 
				
			||||||
clap = { version = "4.3.2" , features = [ "derive" ] }
 | 
					clap = { version = "4.3.2" , features = [ "derive" ] }
 | 
				
			||||||
rusqlite = { version = "0.29", features = [ "bundled" ] }
 | 
					rusqlite = { version = "0.29", features = [ "bundled" ] }
 | 
				
			||||||
							
								
								
									
										113
									
								
								src/app.rs
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								src/app.rs
									
									
									
									
									
								
							@ -1,42 +1,65 @@
 | 
				
			|||||||
use anyhow::Error;
 | 
					use anyhow::Error;
 | 
				
			||||||
use int_enum::IntEnum;
 | 
					use int_enum::IntEnum;
 | 
				
			||||||
use rusqlite::Connection;
 | 
					use rusqlite::Connection;
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					 | 
				
			||||||
use std::cmp::min;
 | 
					use std::cmp::min;
 | 
				
			||||||
use tui_textarea::TextArea;
 | 
					use tui_textarea::TextArea;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::db;
 | 
					use crate::db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize)]
 | 
					/// Represents a kanban column containing the tasks and other metadata.
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct Column {
 | 
					pub struct Column {
 | 
				
			||||||
 | 
					    /// Id provided by the database
 | 
				
			||||||
    pub id: i64,
 | 
					    pub id: i64,
 | 
				
			||||||
 | 
					    /// The name used for the title in the UI
 | 
				
			||||||
    pub name: String,
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    /// The currently selected [`Task`], which keeps track of the
 | 
				
			||||||
 | 
					    /// user's position in a column when the go from one to another
 | 
				
			||||||
    pub selected_task_idx: usize,
 | 
					    pub selected_task_idx: usize,
 | 
				
			||||||
 | 
					    /// The collection of [`Task`]
 | 
				
			||||||
    pub tasks: Vec<Task>,
 | 
					    pub tasks: Vec<Task>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Default, Deserialize, Serialize, Debug)]
 | 
					/// Basic TODO task with a title and a description.
 | 
				
			||||||
 | 
					#[derive(Clone, Default, Debug)]
 | 
				
			||||||
pub struct Task {
 | 
					pub struct Task {
 | 
				
			||||||
 | 
					    /// Id provided by the database
 | 
				
			||||||
    pub id: i64,
 | 
					    pub id: i64,
 | 
				
			||||||
 | 
					    /// Title of the [`Task`]
 | 
				
			||||||
    pub title: String,
 | 
					    pub title: String,
 | 
				
			||||||
 | 
					    /// Description of the [`Task`]
 | 
				
			||||||
    pub description: String,
 | 
					    pub description: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The number of TaskEditFocus variants, used so we can "wrap around"
 | 
				
			||||||
 | 
					/// with modulo when cycling through tasks with Tab/Backtab.
 | 
				
			||||||
pub const EDIT_WINDOW_FOCUS_STATES: i8 = 4;
 | 
					pub const EDIT_WINDOW_FOCUS_STATES: i8 = 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Used to track the focus of the form field in the task edit window.
 | 
				
			||||||
#[repr(i8)]
 | 
					#[repr(i8)]
 | 
				
			||||||
#[derive(Debug, IntEnum, Copy, Clone)]
 | 
					#[derive(Debug, IntEnum, Copy, Clone)]
 | 
				
			||||||
pub enum TaskEditFocus {
 | 
					pub enum TaskEditFocus {
 | 
				
			||||||
 | 
					    /// Title text input line
 | 
				
			||||||
    Title = 0,
 | 
					    Title = 0,
 | 
				
			||||||
 | 
					    /// Description text input box
 | 
				
			||||||
    Description = 1,
 | 
					    Description = 1,
 | 
				
			||||||
 | 
					    /// Confirm changes button
 | 
				
			||||||
    ConfirmBtn = 2,
 | 
					    ConfirmBtn = 2,
 | 
				
			||||||
 | 
					    /// Cancel changes button
 | 
				
			||||||
    CancelBtn = 3,
 | 
					    CancelBtn = 3,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Represents the transient state of a task while it is being editing
 | 
				
			||||||
 | 
					/// by the user through the UI.
 | 
				
			||||||
pub struct TaskState<'a> {
 | 
					pub struct TaskState<'a> {
 | 
				
			||||||
 | 
					    /// The title of the Task
 | 
				
			||||||
    pub title: TextArea<'a>,
 | 
					    pub title: TextArea<'a>,
 | 
				
			||||||
 | 
					    /// The description of the Task
 | 
				
			||||||
    pub description: TextArea<'a>,
 | 
					    pub description: TextArea<'a>,
 | 
				
			||||||
 | 
					    /// Where the current focus of the task edit form is
 | 
				
			||||||
    pub focus: TaskEditFocus,
 | 
					    pub focus: TaskEditFocus,
 | 
				
			||||||
 | 
					    /// Used to decide if the user is editing an existing task or
 | 
				
			||||||
 | 
					    /// creating a new one
 | 
				
			||||||
    pub is_edit: bool,
 | 
					    pub is_edit: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -51,12 +74,21 @@ impl Default for TaskState<'_> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Holds the application's state, including all columns and the
 | 
				
			||||||
 | 
					/// database connection.
 | 
				
			||||||
pub struct State<'a> {
 | 
					pub struct State<'a> {
 | 
				
			||||||
 | 
					    /// The name of the project, currently derived from the name of
 | 
				
			||||||
 | 
					    /// the current working directory
 | 
				
			||||||
    pub project_name: String,
 | 
					    pub project_name: String,
 | 
				
			||||||
 | 
					    /// The index of the currently selected [`Column`]
 | 
				
			||||||
    pub selected_column_idx: usize,
 | 
					    pub selected_column_idx: usize,
 | 
				
			||||||
 | 
					    /// A vec of all the [`Column`]s
 | 
				
			||||||
    pub columns: Vec<Column>,
 | 
					    pub columns: Vec<Column>,
 | 
				
			||||||
 | 
					    /// The [`db::DBConn`] wrapping a [`rusqlite::Connection`]
 | 
				
			||||||
    pub db_conn: db::DBConn,
 | 
					    pub db_conn: db::DBConn,
 | 
				
			||||||
 | 
					    /// Flag to check on each loop whether we should exit the app
 | 
				
			||||||
    pub quit: bool,
 | 
					    pub quit: bool,
 | 
				
			||||||
 | 
					    /// If [`Some(TaskState)`] then we are in the task edit form window
 | 
				
			||||||
    pub task_edit_state: Option<TaskState<'a>>,
 | 
					    pub task_edit_state: Option<TaskState<'a>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -87,33 +119,44 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns a reference to the currently selected [`Column`].
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn get_selected_column(&self) -> &Column {
 | 
					    pub fn get_selected_column(&self) -> &Column {
 | 
				
			||||||
        &self.columns[self.selected_column_idx]
 | 
					        &self.columns[self.selected_column_idx]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns a mutable reference to the currently selected
 | 
				
			||||||
 | 
					    /// [`Column`].
 | 
				
			||||||
    pub fn get_selected_column_mut(&mut self) -> &mut Column {
 | 
					    pub fn get_selected_column_mut(&mut self) -> &mut Column {
 | 
				
			||||||
        &mut self.columns[self.selected_column_idx]
 | 
					        &mut self.columns[self.selected_column_idx]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn select_previous_column(&mut self) -> Result<(), Error> {
 | 
					    /// Selects the [`Column`] on the left. Does nothing if on the
 | 
				
			||||||
 | 
					    /// first column.
 | 
				
			||||||
 | 
					    pub fn select_column_left(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        self.selected_column_idx = self.selected_column_idx.saturating_sub(1);
 | 
					        self.selected_column_idx = self.selected_column_idx.saturating_sub(1);
 | 
				
			||||||
        self.db_conn.set_selected_column(self.selected_column_idx)
 | 
					        self.db_conn.set_selected_column(self.selected_column_idx)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn select_next_column(&mut self) -> Result<(), Error> {
 | 
					    /// Selects the [`Column`] on the right. Does nothing if on the
 | 
				
			||||||
 | 
					    /// last column.
 | 
				
			||||||
 | 
					    pub fn select_column_right(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        self.selected_column_idx = min(self.selected_column_idx + 1, self.columns.len() - 1);
 | 
					        self.selected_column_idx = min(self.selected_column_idx + 1, self.columns.len() - 1);
 | 
				
			||||||
        self.db_conn.set_selected_column(self.selected_column_idx)
 | 
					        self.db_conn.set_selected_column(self.selected_column_idx)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns a reference to the currently selected [`Task`].
 | 
				
			||||||
 | 
					    /// Returns `None` if the current [`Column::tasks`] is empty.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn get_selected_task(&self) -> Option<&Task> {
 | 
					    pub fn get_selected_task(&self) -> Option<&Task> {
 | 
				
			||||||
        let column = self.get_selected_column();
 | 
					        let column = self.get_selected_column();
 | 
				
			||||||
        column.tasks.get(column.selected_task_idx)
 | 
					        column.tasks.get(column.selected_task_idx)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns a reference to the [`Task`] above the current one.
 | 
				
			||||||
 | 
					    /// Returns `None` if it's the first task on the list
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn get_previous_task(&self) -> Option<&Task> {
 | 
					    pub fn get_task_above(&self) -> Option<&Task> {
 | 
				
			||||||
        let column = self.get_selected_column();
 | 
					        let column = self.get_selected_column();
 | 
				
			||||||
        if column.selected_task_idx > 0 {
 | 
					        if column.selected_task_idx > 0 {
 | 
				
			||||||
            column.tasks.get(column.selected_task_idx - 1)
 | 
					            column.tasks.get(column.selected_task_idx - 1)
 | 
				
			||||||
@ -122,18 +165,25 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns a reference to the [`Task`] below the current one.
 | 
				
			||||||
 | 
					    /// Returns `None` if it's the last task on the list
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn get_next_task(&self) -> Option<&Task> {
 | 
					    pub fn get_task_below(&self) -> Option<&Task> {
 | 
				
			||||||
        let column = self.get_selected_column();
 | 
					        let column = self.get_selected_column();
 | 
				
			||||||
        column.tasks.get(column.selected_task_idx + 1)
 | 
					        column.tasks.get(column.selected_task_idx + 1)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns a mutable reference to the currently selected
 | 
				
			||||||
 | 
					    /// [`Task`]. Returns `None` if the current [`Column::tasks`] is
 | 
				
			||||||
 | 
					    /// empty.
 | 
				
			||||||
    pub fn get_selected_task_mut(&mut self) -> Option<&mut Task> {
 | 
					    pub fn get_selected_task_mut(&mut self) -> Option<&mut Task> {
 | 
				
			||||||
        let column = self.get_selected_column_mut();
 | 
					        let column = self.get_selected_column_mut();
 | 
				
			||||||
        column.tasks.get_mut(column.selected_task_idx)
 | 
					        column.tasks.get_mut(column.selected_task_idx)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn select_previous_task(&mut self) -> Result<(), Error> {
 | 
					    /// Selects the [`Task`] above the current one. Does nothing if
 | 
				
			||||||
 | 
					    /// it's the first task on the list
 | 
				
			||||||
 | 
					    pub fn select_task_above(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        let column = self.get_selected_column_mut();
 | 
					        let column = self.get_selected_column_mut();
 | 
				
			||||||
        column.selected_task_idx = column.selected_task_idx.saturating_sub(1);
 | 
					        column.selected_task_idx = column.selected_task_idx.saturating_sub(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -144,7 +194,9 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn select_next_task(&mut self) -> Result<(), Error> {
 | 
					    /// Selects the [`Task`] below the current one. Does nothing if
 | 
				
			||||||
 | 
					    /// it's the last task on the list
 | 
				
			||||||
 | 
					    pub fn select_task_below(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        let column = self.get_selected_column_mut();
 | 
					        let column = self.get_selected_column_mut();
 | 
				
			||||||
        column.selected_task_idx = min(
 | 
					        column.selected_task_idx = min(
 | 
				
			||||||
            column.selected_task_idx + 1,
 | 
					            column.selected_task_idx + 1,
 | 
				
			||||||
@ -158,6 +210,8 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Selects the [`Task`] at the beginning of the list, no matter
 | 
				
			||||||
 | 
					    /// where you are in the current [`Column`].
 | 
				
			||||||
    pub fn select_first_task(&mut self) -> Result<(), Error> {
 | 
					    pub fn select_first_task(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        let column = self.get_selected_column_mut();
 | 
					        let column = self.get_selected_column_mut();
 | 
				
			||||||
        column.selected_task_idx = 0;
 | 
					        column.selected_task_idx = 0;
 | 
				
			||||||
@ -169,6 +223,8 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Selects the [`Task`] at the end of the list, no matter
 | 
				
			||||||
 | 
					    /// where you are in the current [`Column`].
 | 
				
			||||||
    pub fn select_last_task(&mut self) -> Result<(), Error> {
 | 
					    pub fn select_last_task(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        let column = self.get_selected_column_mut();
 | 
					        let column = self.get_selected_column_mut();
 | 
				
			||||||
        column.selected_task_idx = column.tasks.len().saturating_sub(1);
 | 
					        column.selected_task_idx = column.tasks.len().saturating_sub(1);
 | 
				
			||||||
@ -180,6 +236,9 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Helper method to construct a [`TaskState`]. Used when we are
 | 
				
			||||||
 | 
					    /// going to edit an existing [`Task`]. Returns `None` if the
 | 
				
			||||||
 | 
					    /// [`Column`] is empty.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn get_task_state_from_current(&self) -> Option<TaskState<'a>> {
 | 
					    pub fn get_task_state_from_current(&self) -> Option<TaskState<'a>> {
 | 
				
			||||||
        self.get_selected_task().map(|t| TaskState {
 | 
					        self.get_selected_task().map(|t| TaskState {
 | 
				
			||||||
@ -190,20 +249,25 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Moves the current [`Task`] up the list towards the top. Does
 | 
				
			||||||
 | 
					    /// nothing if it's the first task.
 | 
				
			||||||
    pub fn move_task_up(&mut self) -> Result<(), Error> {
 | 
					    pub fn move_task_up(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        self.move_task(false)
 | 
					        self.move_task(false)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Moves the current [`Task`] down the list towards the bottom. Does
 | 
				
			||||||
 | 
					    /// nothing if it's the last task.
 | 
				
			||||||
    pub fn move_task_down(&mut self) -> Result<(), Error> {
 | 
					    pub fn move_task_down(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        self.move_task(true)
 | 
					        self.move_task(true)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Returns the move task down of this [`State`].
 | 
					    /// Private function to handle saving the current [`Task`]'s
 | 
				
			||||||
    pub fn move_task(&mut self, is_down: bool) -> Result<(), Error> {
 | 
					    /// state.
 | 
				
			||||||
 | 
					    fn move_task(&mut self, is_down: bool) -> Result<(), Error> {
 | 
				
			||||||
        let other_task = if is_down {
 | 
					        let other_task = if is_down {
 | 
				
			||||||
            self.get_next_task()
 | 
					            self.get_task_below()
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            self.get_previous_task()
 | 
					            self.get_task_above()
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        if let (Some(task1), Some(task2)) = (self.get_selected_task(), other_task) {
 | 
					        if let (Some(task1), Some(task2)) = (self.get_selected_task(), other_task) {
 | 
				
			||||||
            let t1_id = task1.id;
 | 
					            let t1_id = task1.id;
 | 
				
			||||||
@ -228,11 +292,15 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn move_task_previous_column(&mut self) -> Result<(), Error> {
 | 
					    /// Moves the current [`Task`] to the [`Column`] on the left. Does
 | 
				
			||||||
 | 
					    /// nothing if it's the first column.
 | 
				
			||||||
 | 
					    pub fn move_task_column_left(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        self.move_task_to_column(false)
 | 
					        self.move_task_to_column(false)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn move_task_next_column(&mut self) -> Result<(), Error> {
 | 
					    /// Moves the current [`Task`] to the [`Column`] on the right. Does
 | 
				
			||||||
 | 
					    /// nothing if it's the last column.
 | 
				
			||||||
 | 
					    pub fn move_task_column_right(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        self.move_task_to_column(true)
 | 
					        self.move_task_to_column(true)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -249,13 +317,13 @@ impl<'a> State<'a> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Only move it if it was the last task
 | 
					        // Only move it if it was the last task
 | 
				
			||||||
        if first_col.selected_task_idx == first_col.tasks.len() {
 | 
					        if first_col.selected_task_idx == first_col.tasks.len() {
 | 
				
			||||||
            self.select_previous_task()?;
 | 
					            self.select_task_above()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if move_right {
 | 
					        if move_right {
 | 
				
			||||||
            self.select_next_column()?;
 | 
					            self.select_column_right()?;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            self.select_previous_column()?;
 | 
					            self.select_column_left()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let col = self.get_selected_column_mut();
 | 
					        let col = self.get_selected_column_mut();
 | 
				
			||||||
@ -269,6 +337,8 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Inserts a new [`Task`] into [`Column::tasks`] at the bottom of
 | 
				
			||||||
 | 
					    /// the list and saves the state to the DB.
 | 
				
			||||||
    pub fn add_new_task(&mut self, title: String, description: String) -> Result<(), Error> {
 | 
					    pub fn add_new_task(&mut self, title: String, description: String) -> Result<(), Error> {
 | 
				
			||||||
        let col_id = self.get_selected_column().id;
 | 
					        let col_id = self.get_selected_column().id;
 | 
				
			||||||
        let task = self.db_conn.create_new_task(title, description, col_id)?;
 | 
					        let task = self.db_conn.create_new_task(title, description, col_id)?;
 | 
				
			||||||
@ -283,6 +353,8 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Edits the selected [`Task`] changing only it's title and/or
 | 
				
			||||||
 | 
					    /// description. Does nothing if the [`Column`] is empty.
 | 
				
			||||||
    pub fn edit_task(&mut self, title: String, description: String) -> Result<(), Error> {
 | 
					    pub fn edit_task(&mut self, title: String, description: String) -> Result<(), Error> {
 | 
				
			||||||
        if let Some(selected_task) = self.get_selected_task_mut() {
 | 
					        if let Some(selected_task) = self.get_selected_task_mut() {
 | 
				
			||||||
            selected_task.title = title;
 | 
					            selected_task.title = title;
 | 
				
			||||||
@ -294,7 +366,8 @@ impl<'a> State<'a> {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Delete the currently selected task from the selected column
 | 
					    /// Deletes the selected [`Task`] from the list. Does nothing if
 | 
				
			||||||
 | 
					    /// the [`Column`] is empty.
 | 
				
			||||||
    pub fn delete_task(&mut self) -> Result<(), Error> {
 | 
					    pub fn delete_task(&mut self) -> Result<(), Error> {
 | 
				
			||||||
        if let Some(task) = self.get_selected_task() {
 | 
					        if let Some(task) = self.get_selected_task() {
 | 
				
			||||||
            let task_id = task.id;
 | 
					            let task_id = task.id;
 | 
				
			||||||
@ -305,7 +378,7 @@ impl<'a> State<'a> {
 | 
				
			|||||||
            column.tasks.remove(task_idx);
 | 
					            column.tasks.remove(task_idx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if column.selected_task_idx >= column.tasks.len() {
 | 
					            if column.selected_task_idx >= column.tasks.len() {
 | 
				
			||||||
                self.select_previous_task()?;
 | 
					                self.select_task_above()?;
 | 
				
			||||||
                task_idx = task_idx.saturating_sub(1);
 | 
					                task_idx = task_idx.saturating_sub(1);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										99
									
								
								src/db.rs
									
									
									
									
									
								
							
							
						
						
									
										99
									
								
								src/db.rs
									
									
									
									
									
								
							@ -3,7 +3,13 @@ use anyhow::Error;
 | 
				
			|||||||
use rusqlite::{params, Connection, Result};
 | 
					use rusqlite::{params, Connection, Result};
 | 
				
			||||||
use std::ops::{Deref, DerefMut};
 | 
					use std::ops::{Deref, DerefMut};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct DBConn(pub Connection);
 | 
					/// Simple one field struct to wrap a  [`rusqlite::Connection`] so we
 | 
				
			||||||
 | 
					/// can assign our own methods.
 | 
				
			||||||
 | 
					pub struct DBConn(
 | 
				
			||||||
 | 
					    /// Unfortunately remains public for now so we can do integration tests
 | 
				
			||||||
 | 
					    /// and reuse to simulate exiting and relaunching the app.
 | 
				
			||||||
 | 
					    pub Connection
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl DerefMut for DBConn {
 | 
					impl DerefMut for DBConn {
 | 
				
			||||||
    fn deref_mut(&mut self) -> &mut Self::Target {
 | 
					    fn deref_mut(&mut self) -> &mut Self::Target {
 | 
				
			||||||
@ -24,22 +30,21 @@ impl DBConn {
 | 
				
			|||||||
        DBConn(conn)
 | 
					        DBConn(conn)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// .
 | 
					    /// Query tasks in a [`Column`] by using the column's [`Column::id`].
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Errors
 | 
					    /// # Errors
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// This function will return an error if something is wrong with the SQL
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn get_tasks_by_column(&self, column_name: &String) -> Result<Vec<Task>> {
 | 
					    pub fn get_tasks_by_column(&self, column_id: i64) -> Result<Vec<Task>> {
 | 
				
			||||||
        let mut stmt = self.prepare(
 | 
					        let mut stmt = self.prepare(
 | 
				
			||||||
            r#"
 | 
					            r#"
 | 
				
			||||||
            select task.id, title, description from task
 | 
					            select task.id, title, description from task
 | 
				
			||||||
            join kb_column on column_id = kb_column.id
 | 
					            where column_id = ?1
 | 
				
			||||||
            where kb_column.name = ?1
 | 
					 | 
				
			||||||
            order by sort_order
 | 
					            order by sort_order
 | 
				
			||||||
        "#,
 | 
					        "#,
 | 
				
			||||||
        )?;
 | 
					        )?;
 | 
				
			||||||
        let mut tasks = Vec::new();
 | 
					        let mut tasks = Vec::new();
 | 
				
			||||||
        let rows = stmt.query_map([column_name], |row| {
 | 
					        let rows = stmt.query_map([column_id], |row| {
 | 
				
			||||||
            Ok(Task {
 | 
					            Ok(Task {
 | 
				
			||||||
                id: row.get(0)?,
 | 
					                id: row.get(0)?,
 | 
				
			||||||
                title: row.get(1)?,
 | 
					                title: row.get(1)?,
 | 
				
			||||||
@ -52,20 +57,22 @@ impl DBConn {
 | 
				
			|||||||
        Ok(tasks)
 | 
					        Ok(tasks)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// .
 | 
					    /// Uses [get_tasks_by_column][`DBConn::get_tasks_by_column`] over
 | 
				
			||||||
 | 
					    /// a loop to get all [`Column`] populated with the vec of.
 | 
				
			||||||
 | 
					    /// [`Task`]
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Errors
 | 
					    /// # Errors
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// This function will return an error if there are issues with the SQL
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn get_all_columns(&self) -> Result<Vec<Column>> {
 | 
					    pub fn get_all_columns(&self) -> Result<Vec<Column>> {
 | 
				
			||||||
        let mut stmt = self.prepare("select id, name, selected_task from kb_column")?;
 | 
					        let mut stmt = self.prepare("select id, name, selected_task from kb_column")?;
 | 
				
			||||||
        let columns = stmt
 | 
					        let columns = stmt
 | 
				
			||||||
            .query_map((), |row| {
 | 
					            .query_map((), |row| {
 | 
				
			||||||
                let name = row.get(1)?;
 | 
					                let id = row.get(0)?;
 | 
				
			||||||
                Ok(Column {
 | 
					                Ok(Column {
 | 
				
			||||||
                    id: row.get(0)?,
 | 
					                    id,
 | 
				
			||||||
                    tasks: self.get_tasks_by_column(&name)?,
 | 
					                    tasks: self.get_tasks_by_column(id)?,
 | 
				
			||||||
                    name,
 | 
					                    name: row.get(1)?,
 | 
				
			||||||
                    selected_task_idx: row.get(2)?,
 | 
					                    selected_task_idx: row.get(2)?,
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            })?
 | 
					            })?
 | 
				
			||||||
@ -74,11 +81,12 @@ impl DBConn {
 | 
				
			|||||||
        Ok(columns)
 | 
					        Ok(columns)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// .
 | 
					    /// Insert a new task into the DB given a title and description,
 | 
				
			||||||
 | 
					    /// then return the [`Task`] with the ID provided by the DB.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Panics
 | 
					    /// # Errors
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Panics if something goes wrong with the SQL
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn create_new_task(
 | 
					    pub fn create_new_task(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        title: String,
 | 
					        title: String,
 | 
				
			||||||
@ -102,22 +110,22 @@ impl DBConn {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// .
 | 
					    /// Deletes a [`Task`] given it's ID.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Panics
 | 
					    /// # Errors
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Panics if something goes wrong with the SQL
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn delete_task(&self, task_id: i64) -> Result<()> {
 | 
					    pub fn delete_task(&self, task_id: i64) -> Result<()> {
 | 
				
			||||||
        let mut stmt = self.prepare("delete from task where id = ?1")?;
 | 
					        let mut stmt = self.prepare("delete from task where id = ?1")?;
 | 
				
			||||||
        stmt.execute([task_id])?;
 | 
					        stmt.execute([task_id])?;
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// .
 | 
					    /// Updates an existing [`Task`]'s `title` and `description`.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Panics
 | 
					    /// # Errors
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Panics if something goes wrong with the SQL
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn update_task_text(&self, task: &Task) -> Result<()> {
 | 
					    pub fn update_task_text(&self, task: &Task) -> Result<()> {
 | 
				
			||||||
        let mut stmt =
 | 
					        let mut stmt =
 | 
				
			||||||
            self.prepare("update task set title = ?2, description = ?3 where id = ?1")?;
 | 
					            self.prepare("update task set title = ?2, description = ?3 where id = ?1")?;
 | 
				
			||||||
@ -125,11 +133,11 @@ impl DBConn {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// .
 | 
					    /// Moves a [`Task`] to the target [`Column`] and updates the sorting order.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Panics
 | 
					    /// # Errors
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Panics if something goes wrong with the SQL
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn move_task_to_column(&self, task: &Task, target_column: &Column) -> Result<()> {
 | 
					    pub fn move_task_to_column(&self, task: &Task, target_column: &Column) -> Result<()> {
 | 
				
			||||||
        let mut stmt = self
 | 
					        let mut stmt = self
 | 
				
			||||||
            .prepare(
 | 
					            .prepare(
 | 
				
			||||||
@ -148,7 +156,7 @@ impl DBConn {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// This is a helper function in case we need to debug sort_order, because I ran into
 | 
					    /// This is a helper function in case we need to debug sort_order, because I ran into
 | 
				
			||||||
    /// a bug when I forgot to insert the sort_order when creating a task
 | 
					    /// a bug when I forgot to insert the sort_order when creating a task.
 | 
				
			||||||
    #[allow(dead_code)]
 | 
					    #[allow(dead_code)]
 | 
				
			||||||
    fn get_sort_order(&self) -> Result<Vec<(i32, String, usize)>> {
 | 
					    fn get_sort_order(&self) -> Result<Vec<(i32, String, usize)>> {
 | 
				
			||||||
        let mut stmt = self.prepare(
 | 
					        let mut stmt = self.prepare(
 | 
				
			||||||
@ -162,11 +170,14 @@ impl DBConn {
 | 
				
			|||||||
        Ok(tasks)
 | 
					        Ok(tasks)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// .
 | 
					    /// The order of a [`Task`] in a [`Column`] needs to be saved to
 | 
				
			||||||
 | 
					    /// the DB because SQLite doesn't have a way to handle the
 | 
				
			||||||
 | 
					    /// ordering the internal [`Vec<Task>`] has. This takes the
 | 
				
			||||||
 | 
					    /// current sorting order of two tasks and swaps them.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// # Panics
 | 
					    /// # Errors
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Panics if something goes wrong with the SQL
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn swap_task_order(&mut self, task1_id: i64, task2_id: i64) -> Result<()> {
 | 
					    pub fn swap_task_order(&mut self, task1_id: i64, task2_id: i64) -> Result<()> {
 | 
				
			||||||
        let tx = self.transaction()?;
 | 
					        let tx = self.transaction()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -194,9 +205,13 @@ impl DBConn {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// # Panics
 | 
					    /// Saves the currently selected column's index to `app_state` so
 | 
				
			||||||
 | 
					    /// when the user reloads the project, they start on the
 | 
				
			||||||
 | 
					    /// [`Column`] they were last on.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Panics if something goes wrong with the SQL
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn set_selected_column(&self, column_id: usize) -> Result<(), Error> {
 | 
					    pub fn set_selected_column(&self, column_id: usize) -> Result<(), Error> {
 | 
				
			||||||
        let mut stmt =
 | 
					        let mut stmt =
 | 
				
			||||||
            self.prepare("insert or replace into app_state(key, value) values (?1, ?2)")?;
 | 
					            self.prepare("insert or replace into app_state(key, value) values (?1, ?2)")?;
 | 
				
			||||||
@ -204,9 +219,11 @@ impl DBConn {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// # Panics
 | 
					    /// Get's the user's last selected [`Column`] before exiting.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Panics if something goes wrong with the SQL
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn get_selected_column(&self) -> Result<usize> {
 | 
					    pub fn get_selected_column(&self) -> Result<usize> {
 | 
				
			||||||
        let mut stmt = self.prepare("select value from app_state where key = ?1")?;
 | 
					        let mut stmt = self.prepare("select value from app_state where key = ?1")?;
 | 
				
			||||||
        stmt.query_row(["selected_column"], |row| {
 | 
					        stmt.query_row(["selected_column"], |row| {
 | 
				
			||||||
@ -216,18 +233,26 @@ impl DBConn {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// # Panics
 | 
					    /// Saves the index currently selected [`Task`] in a [`Column`] so
 | 
				
			||||||
 | 
					    /// when the user reloads the project, each column selects the has
 | 
				
			||||||
 | 
					    /// the last selected task before switching to another column or
 | 
				
			||||||
 | 
					    /// exiting the app.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Panics if something goes wrong with the SQL
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn set_selected_task_for_column(&self, task_idx: usize, column_id: i64) -> Result<()> {
 | 
					    pub fn set_selected_task_for_column(&self, task_idx: usize, column_id: i64) -> Result<()> {
 | 
				
			||||||
        let mut stmt = self.prepare("update kb_column set selected_task = ?2 where id = ?1")?;
 | 
					        let mut stmt = self.prepare("update kb_column set selected_task = ?2 where id = ?1")?;
 | 
				
			||||||
        stmt.execute((column_id, task_idx))?;
 | 
					        stmt.execute((column_id, task_idx))?;
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// # Panics
 | 
					    /// Get's each [`Column`]'s 's last selected [`Task`] before
 | 
				
			||||||
 | 
					    /// switching or exiting.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// Panics if something goes wrong with the SQL
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Returns an error if something is wrong with the SQL.
 | 
				
			||||||
    pub fn get_selected_task_for_column(&self, column_id: i32) -> Result<usize> {
 | 
					    pub fn get_selected_task_for_column(&self, column_id: i32) -> Result<usize> {
 | 
				
			||||||
        let mut stmt = self.prepare("select selected_task from kb_column where key = ?1")?;
 | 
					        let mut stmt = self.prepare("select selected_task from kb_column where key = ?1")?;
 | 
				
			||||||
        stmt.query_row([column_id], |row| row.get(0))
 | 
					        stmt.query_row([column_id], |row| row.get(0))
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										30
									
								
								src/input.rs
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								src/input.rs
									
									
									
									
									
								
							@ -5,12 +5,11 @@ use crossterm::event::{Event, KeyCode};
 | 
				
			|||||||
use int_enum::IntEnum;
 | 
					use int_enum::IntEnum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn cycle_focus(task: &mut TaskState<'_>, forward: bool) -> Result<(), Error> {
 | 
					pub fn cycle_focus(task: &mut TaskState<'_>, forward: bool) -> Result<(), Error> {
 | 
				
			||||||
    let cycle;
 | 
					    let cycle = if forward {
 | 
				
			||||||
    if forward {
 | 
					        (task.focus.int_value() + 1) % EDIT_WINDOW_FOCUS_STATES
 | 
				
			||||||
        cycle = (task.focus.int_value() + 1) % EDIT_WINDOW_FOCUS_STATES;
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        cycle = (task.focus.int_value() - 1) % EDIT_WINDOW_FOCUS_STATES;
 | 
					        (task.focus.int_value() - 1) % EDIT_WINDOW_FOCUS_STATES
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
    task.focus = TaskEditFocus::from_int(cycle)?;
 | 
					    task.focus = TaskEditFocus::from_int(cycle)?;
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -66,14 +65,14 @@ pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent) -> Result<(
 | 
				
			|||||||
pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) -> Result<(), Error> {
 | 
					pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) -> Result<(), Error> {
 | 
				
			||||||
    match key.code {
 | 
					    match key.code {
 | 
				
			||||||
        KeyCode::Char('q') => Ok(state.quit = true),
 | 
					        KeyCode::Char('q') => Ok(state.quit = true),
 | 
				
			||||||
        KeyCode::Char('h') | KeyCode::Left => state.select_previous_column(),
 | 
					        KeyCode::Char('h') | KeyCode::Left => state.select_column_left(),
 | 
				
			||||||
        KeyCode::Char('j') | KeyCode::Down => state.select_next_task(),
 | 
					        KeyCode::Char('j') | KeyCode::Down => state.select_task_below(),
 | 
				
			||||||
        KeyCode::Char('k') | KeyCode::Up => state.select_previous_task(),
 | 
					        KeyCode::Char('k') | KeyCode::Up => state.select_task_above(),
 | 
				
			||||||
        KeyCode::Char('l') | KeyCode::Right => state.select_next_column(),
 | 
					        KeyCode::Char('l') | KeyCode::Right => state.select_column_right(),
 | 
				
			||||||
        KeyCode::Char('g') => state.select_first_task(),
 | 
					        KeyCode::Char('g') => state.select_first_task(),
 | 
				
			||||||
        KeyCode::Char('G') => state.select_last_task(),
 | 
					        KeyCode::Char('G') => state.select_last_task(),
 | 
				
			||||||
        KeyCode::Char('H') => state.move_task_previous_column(),
 | 
					        KeyCode::Char('H') => state.move_task_column_left(),
 | 
				
			||||||
        KeyCode::Char('L') => state.move_task_next_column(),
 | 
					        KeyCode::Char('L') => state.move_task_column_right(),
 | 
				
			||||||
        KeyCode::Char('J') => state.move_task_down(),
 | 
					        KeyCode::Char('J') => state.move_task_down(),
 | 
				
			||||||
        KeyCode::Char('K') => state.move_task_up(),
 | 
					        KeyCode::Char('K') => state.move_task_up(),
 | 
				
			||||||
        KeyCode::Char('n') => Ok(state.task_edit_state = Some(TaskState::default())),
 | 
					        KeyCode::Char('n') => Ok(state.task_edit_state = Some(TaskState::default())),
 | 
				
			||||||
@ -83,10 +82,15 @@ pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) -> Result<(), Er
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Takes the app's [`State`] and uses [`event::read`] to get the current keypress.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
/// # Errors
 | 
					/// # Errors
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// Crossterm `event::read()` might return an error
 | 
					/// Most of the applications errors will be bubbled up to this layer,
 | 
				
			||||||
pub fn handle(state: &mut State<'_>) -> Result<(), Error> {
 | 
					/// including all database related ones
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Crossterm `event::read()` might return an error,
 | 
				
			||||||
 | 
					pub fn handle_user_keypress(state: &mut State<'_>) -> Result<(), Error> {
 | 
				
			||||||
    if let Event::Key(key) = event::read()? {
 | 
					    if let Event::Key(key) = event::read()? {
 | 
				
			||||||
        if state.task_edit_state.is_some() {
 | 
					        if state.task_edit_state.is_some() {
 | 
				
			||||||
            handle_task_edit(state, key)?;
 | 
					            handle_task_edit(state, key)?;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										18
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/lib.rs
									
									
									
									
									
								
							@ -1,3 +1,17 @@
 | 
				
			|||||||
 | 
					//! Manage your TODOs with kanban columns right from the terminal.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! kanban-tui is a TUI based application using [`ratatui`] for
 | 
				
			||||||
 | 
					//! rendering the UI and capturing user input interaction, with
 | 
				
			||||||
 | 
					//! [`Crossterm`] as the lower-level backend system for terminal
 | 
				
			||||||
 | 
					//! text-based interfaces. The data is saved to a SQLite database
 | 
				
			||||||
 | 
					//! ideally placed in the root of your project. For this the
 | 
				
			||||||
 | 
					//! [`rusqlite`] crate provides the bindings to handle all the data
 | 
				
			||||||
 | 
					//! persistence.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! [`ratatui`]: https://crates.io/crates/ratatui
 | 
				
			||||||
 | 
					//! [`Crossterm`]: https://crates.io/crates/crossterm
 | 
				
			||||||
 | 
					//! [`rusqlite`]: https://crates.io/crates/rusqlite
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#![deny(rust_2018_idioms)]
 | 
					#![deny(rust_2018_idioms)]
 | 
				
			||||||
mod app;
 | 
					mod app;
 | 
				
			||||||
mod db;
 | 
					mod db;
 | 
				
			||||||
@ -6,5 +20,5 @@ mod ui;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub use app::*;
 | 
					pub use app::*;
 | 
				
			||||||
pub use db::*;
 | 
					pub use db::*;
 | 
				
			||||||
pub use input::handle;
 | 
					pub use input::handle_user_keypress;
 | 
				
			||||||
pub use ui::draw;
 | 
					pub use ui::draw_ui_from_state;
 | 
				
			||||||
 | 
				
			|||||||
@ -49,8 +49,8 @@ fn main() -> anyhow::Result<(), Box<dyn Error>> {
 | 
				
			|||||||
    let mut terminal = Terminal::new(backend)?;
 | 
					    let mut terminal = Terminal::new(backend)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while !state.quit {
 | 
					    while !state.quit {
 | 
				
			||||||
        terminal.draw(|f| kanban_tui::draw(f, &mut state))?;
 | 
					        terminal.draw(|f| kanban_tui::draw_ui_from_state(f, &mut state))?;
 | 
				
			||||||
        kanban_tui::handle(&mut state)?;
 | 
					        kanban_tui::handle_user_keypress(&mut state)?;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // restore terminal
 | 
					    // restore terminal
 | 
				
			||||||
 | 
				
			|||||||
@ -216,14 +216,16 @@ fn draw_project_stats<B: Backend>(f: &mut Frame<'_, B>, area: Rect, state: &mut
 | 
				
			|||||||
    f.render_widget(list, area);
 | 
					    f.render_widget(list, area);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Macro to generate keybindings string at compile time
 | 
					/// Macro to generate the app's keybindings string at compile time
 | 
				
			||||||
macro_rules! unroll {
 | 
					macro_rules! unroll {
 | 
				
			||||||
    (($first_a:literal, $first_b:literal), $(($a:literal, $b:literal)),*) => {
 | 
					    (($first_a:literal, $first_b:literal), $(($a:literal, $b:literal)),*) => {
 | 
				
			||||||
        concat!(concat!($first_a, ": ", $first_b) $(," | ", concat!($a, ": ", $b))*)
 | 
					        concat!(concat!($first_a, ": ", $first_b) $(," | ", concat!($a, ": ", $b))*)
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn draw<B: Backend>(f: &mut Frame<'_, B>, state: &mut State<'_>) {
 | 
					/// Takes the app's [`State`] so [ratatui][`tui`] can render it to the
 | 
				
			||||||
 | 
					/// terminal screen
 | 
				
			||||||
 | 
					pub fn draw_ui_from_state<B: Backend>(f: &mut Frame<'_, B>, state: &mut State<'_>) {
 | 
				
			||||||
    let main_layout = Layout::default()
 | 
					    let main_layout = Layout::default()
 | 
				
			||||||
        .direction(Direction::Vertical)
 | 
					        .direction(Direction::Vertical)
 | 
				
			||||||
        .constraints(
 | 
					        .constraints(
 | 
				
			||||||
 | 
				
			|||||||
@ -25,10 +25,10 @@ mod app_tests {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        state.add_new_task(String::from("T1"), String::from("D1"))?;
 | 
					        state.add_new_task(String::from("T1"), String::from("D1"))?;
 | 
				
			||||||
        state.add_new_task(String::from("T2"), String::from("D2"))?;
 | 
					        state.add_new_task(String::from("T2"), String::from("D2"))?;
 | 
				
			||||||
        state.select_next_column()?;
 | 
					        state.select_column_right()?;
 | 
				
			||||||
        state.select_next_column()?;
 | 
					        state.select_column_right()?;
 | 
				
			||||||
        state.add_new_task(String::from("T3"), String::from("D3"))?;
 | 
					        state.add_new_task(String::from("T3"), String::from("D3"))?;
 | 
				
			||||||
        state.select_previous_column()?;
 | 
					        state.select_column_left()?;
 | 
				
			||||||
        state.add_new_task(String::from("T4"), String::from("D4"))?;
 | 
					        state.add_new_task(String::from("T4"), String::from("D4"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(state.columns[0].tasks.len(), 2);
 | 
					        assert_eq!(state.columns[0].tasks.len(), 2);
 | 
				
			||||||
@ -63,33 +63,33 @@ mod app_tests {
 | 
				
			|||||||
        state.move_task_down()?;
 | 
					        state.move_task_down()?;
 | 
				
			||||||
        state.move_task_down()?;
 | 
					        state.move_task_down()?;
 | 
				
			||||||
        for _ in 0..10 {
 | 
					        for _ in 0..10 {
 | 
				
			||||||
            state.select_next_column()?;
 | 
					            state.select_column_right()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for _ in 0..10 {
 | 
					        for _ in 0..10 {
 | 
				
			||||||
            state.select_previous_column()?;
 | 
					            state.select_column_left()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        state.add_new_task(String::from("T1"), String::from("D1"))?;
 | 
					        state.add_new_task(String::from("T1"), String::from("D1"))?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
				
			||||||
        state.add_new_task(String::from("T2"), String::from("D2"))?;
 | 
					        state.add_new_task(String::from("T2"), String::from("D2"))?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
				
			||||||
        state.select_previous_task()?;
 | 
					        state.select_task_above()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
				
			||||||
        for _ in 0..6 {
 | 
					        for _ in 0..6 {
 | 
				
			||||||
            state.select_next_column()?;
 | 
					            state.select_column_right()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        state.select_previous_column()?;
 | 
					        state.select_column_left()?;
 | 
				
			||||||
        state.add_new_task(String::from("T3"), String::from("D3"))?;
 | 
					        state.add_new_task(String::from("T3"), String::from("D3"))?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
        assert_eq!(state.get_selected_column().name, "Done");
 | 
					        assert_eq!(state.get_selected_column().name, "Done");
 | 
				
			||||||
        for _ in 0..6 {
 | 
					        for _ in 0..6 {
 | 
				
			||||||
            state.select_next_column()?;
 | 
					            state.select_column_right()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for _ in 0..4 {
 | 
					        for _ in 0..4 {
 | 
				
			||||||
            state.select_previous_column()?;
 | 
					            state.select_column_left()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
				
			||||||
        state.select_next_column()?;
 | 
					        state.select_column_right()?;
 | 
				
			||||||
        state.select_next_column()?;
 | 
					        state.select_column_right()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Reload the data from the database then rerun the asserts to
 | 
					        // Reload the data from the database then rerun the asserts to
 | 
				
			||||||
@ -97,13 +97,13 @@ mod app_tests {
 | 
				
			|||||||
        let mut state = State::new(state.db_conn.0)?;
 | 
					        let mut state = State::new(state.db_conn.0)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
        state.select_next_task()?;
 | 
					        state.select_task_below()?;
 | 
				
			||||||
        state.select_next_task()?;
 | 
					        state.select_task_below()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
        state.select_previous_column()?;
 | 
					        state.select_column_left()?;
 | 
				
			||||||
        state.select_previous_column()?;
 | 
					        state.select_column_left()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
				
			||||||
        state.select_next_task()?;
 | 
					        state.select_task_below()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
@ -128,8 +128,8 @@ mod app_tests {
 | 
				
			|||||||
        state.select_first_task()?;
 | 
					        state.select_first_task()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
				
			||||||
        state.select_last_task()?;
 | 
					        state.select_last_task()?;
 | 
				
			||||||
        state.select_previous_task()?;
 | 
					        state.select_task_above()?;
 | 
				
			||||||
        state.select_previous_task()?;
 | 
					        state.select_task_above()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T9");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T9");
 | 
				
			||||||
        for _ in 0..10 {
 | 
					        for _ in 0..10 {
 | 
				
			||||||
            state.move_task_up()?;
 | 
					            state.move_task_up()?;
 | 
				
			||||||
@ -160,24 +160,24 @@ mod app_tests {
 | 
				
			|||||||
        state.add_new_task(String::from("T1"), String::from("D1"))?;
 | 
					        state.add_new_task(String::from("T1"), String::from("D1"))?;
 | 
				
			||||||
        state.add_new_task(String::from("T2"), String::from("D2"))?;
 | 
					        state.add_new_task(String::from("T2"), String::from("D2"))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        state.select_previous_task()?;
 | 
					        state.select_task_above()?;
 | 
				
			||||||
        state.move_task_up()?;
 | 
					        state.move_task_up()?;
 | 
				
			||||||
        state.move_task_down()?;
 | 
					        state.move_task_down()?;
 | 
				
			||||||
        state.move_task_down()?;
 | 
					        state.move_task_down()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(&state.columns[0].tasks[1].title, "T1");
 | 
					        assert_eq!(&state.columns[0].tasks[1].title, "T1");
 | 
				
			||||||
        assert_eq!(&state.columns[0].tasks[0].title, "T2");
 | 
					        assert_eq!(&state.columns[0].tasks[0].title, "T2");
 | 
				
			||||||
        state.select_next_column()?;
 | 
					        state.select_column_right()?;
 | 
				
			||||||
        state.add_new_task(String::from("T3"), String::from("D3"))?;
 | 
					        state.add_new_task(String::from("T3"), String::from("D3"))?;
 | 
				
			||||||
        state.move_task_next_column()?;
 | 
					        state.move_task_column_right()?;
 | 
				
			||||||
        assert_eq!(state.columns[1].tasks.len(), 0);
 | 
					        assert_eq!(state.columns[1].tasks.len(), 0);
 | 
				
			||||||
        assert_eq!(state.columns[2].tasks.len(), 1);
 | 
					        assert_eq!(state.columns[2].tasks.len(), 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for _ in 0..5 {
 | 
					        for _ in 0..5 {
 | 
				
			||||||
            state.move_task_next_column()?;
 | 
					            state.move_task_column_right()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for _ in 0..4 {
 | 
					        for _ in 0..4 {
 | 
				
			||||||
            state.move_task_previous_column()?;
 | 
					            state.move_task_column_left()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(state.columns[0].tasks.len(), 3);
 | 
					        assert_eq!(state.columns[0].tasks.len(), 3);
 | 
				
			||||||
@ -185,25 +185,25 @@ mod app_tests {
 | 
				
			|||||||
        assert_eq!(state.columns[2].tasks.len(), 0);
 | 
					        assert_eq!(state.columns[2].tasks.len(), 0);
 | 
				
			||||||
        assert_eq!(state.columns[3].tasks.len(), 0);
 | 
					        assert_eq!(state.columns[3].tasks.len(), 0);
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
        state.select_next_task()?;
 | 
					        state.select_task_below()?;
 | 
				
			||||||
        state.select_previous_task()?;
 | 
					        state.select_task_above()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
				
			||||||
        state.select_previous_task()?;
 | 
					        state.select_task_above()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
				
			||||||
        state.select_first_task()?;
 | 
					        state.select_first_task()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
				
			||||||
        state.select_last_task()?;
 | 
					        state.select_last_task()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
        state.select_previous_task()?;
 | 
					        state.select_task_above()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Reload the data from the database then rerun the asserts to
 | 
					        // Reload the data from the database then rerun the asserts to
 | 
				
			||||||
        // make sure everything was saved correctly
 | 
					        // make sure everything was saved correctly
 | 
				
			||||||
        let mut state = State::new(state.db_conn.0)?;
 | 
					        let mut state = State::new(state.db_conn.0)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
				
			||||||
        state.select_next_task()?;
 | 
					        state.select_task_below()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
        state.select_next_task()?;
 | 
					        state.select_task_below()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
        state.select_first_task()?;
 | 
					        state.select_first_task()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
				
			||||||
@ -231,7 +231,7 @@ mod app_tests {
 | 
				
			|||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().description, "D1");
 | 
					        assert_eq!(state.get_selected_task().unwrap().description, "D1");
 | 
				
			||||||
        for _ in 0..4 {
 | 
					        for _ in 0..4 {
 | 
				
			||||||
            state.move_task_next_column()?;
 | 
					            state.move_task_column_right()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().description, "D1");
 | 
					        assert_eq!(state.get_selected_task().unwrap().description, "D1");
 | 
				
			||||||
@ -239,7 +239,7 @@ mod app_tests {
 | 
				
			|||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().description, "D3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().description, "D3");
 | 
				
			||||||
        for _ in 0..4 {
 | 
					        for _ in 0..4 {
 | 
				
			||||||
            state.move_task_previous_column()?;
 | 
					            state.move_task_column_left()?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().description, "D3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().description, "D3");
 | 
				
			||||||
@ -283,10 +283,10 @@ mod app_tests {
 | 
				
			|||||||
        state.delete_task()?;
 | 
					        state.delete_task()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
				
			||||||
        state.add_new_task(String::from("T3"), String::from("D3"))?;
 | 
					        state.add_new_task(String::from("T3"), String::from("D3"))?;
 | 
				
			||||||
        state.select_previous_task()?;
 | 
					        state.select_task_above()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T2");
 | 
				
			||||||
        state.delete_task()?;
 | 
					        state.delete_task()?;
 | 
				
			||||||
        state.select_previous_task()?;
 | 
					        state.select_task_above()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
				
			||||||
        state.select_last_task()?;
 | 
					        state.select_last_task()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
@ -296,7 +296,7 @@ mod app_tests {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        state.delete_task()?;
 | 
					        state.delete_task()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T1");
 | 
				
			||||||
        state.select_next_task()?;
 | 
					        state.select_task_below()?;
 | 
				
			||||||
        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
					        assert_eq!(state.get_selected_task().unwrap().title, "T3");
 | 
				
			||||||
        for _ in 0..4 {
 | 
					        for _ in 0..4 {
 | 
				
			||||||
            state.delete_task()?;
 | 
					            state.delete_task()?;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user