feat: Integrate TaskWarrior with config reading and optimized data
loading - Read data.location from ~/.config/task/taskrc (supports TASKDATA/TASKRC env vars) - Add get_overview() method to fetch tasks, projects, tags in one call (3→1 requests) - Replace mock data with real TaskWarrior data - Add logging with env_logger - Improve tag logging output
This commit is contained in:
Generated
+138
@@ -75,6 +75,56 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.100"
|
version = "1.0.100"
|
||||||
@@ -1425,6 +1475,12 @@ 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 = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "command-fds"
|
name = "command-fds"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -2094,6 +2150,29 @@ dependencies = [
|
|||||||
"syn 2.0.111",
|
"syn 2.0.111",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_filter"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.11.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"env_filter",
|
||||||
|
"jiff",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equator"
|
name = "equator"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -3649,6 +3728,12 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@@ -3673,6 +3758,30 @@ version = "1.0.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010"
|
checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-static",
|
||||||
|
"log",
|
||||||
|
"portable-atomic",
|
||||||
|
"portable-atomic-util",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-static"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.111",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.34"
|
version = "0.1.34"
|
||||||
@@ -4427,6 +4536,12 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oo7"
|
name = "oo7"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -4734,6 +4849,21 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7"
|
checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic-util"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postage"
|
name = "postage"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -6370,7 +6500,9 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"dirs 6.0.0",
|
"dirs 6.0.0",
|
||||||
|
"env_logger",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"taskchampion",
|
"taskchampion",
|
||||||
@@ -7031,6 +7163,12 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.19.0"
|
version = "1.19.0"
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ license = "MIT OR Apache-2.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.42", features = ["serde"] }
|
chrono = { version = "0.4.42", features = ["serde"] }
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
|
env_logger = "0.11.0"
|
||||||
gpui = "0.2.2"
|
gpui = "0.2.2"
|
||||||
|
log = "0.4.22"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.147"
|
serde_json = "1.0.147"
|
||||||
taskchampion = "2.0.3"
|
taskchampion = "2.0.3"
|
||||||
|
|||||||
+68
-38
@@ -1,6 +1,7 @@
|
|||||||
use gpui::prelude::*;
|
use gpui::prelude::*;
|
||||||
|
|
||||||
use crate::models::{FilterState, ProjectTree};
|
use crate::models::{FilterState, ProjectTree};
|
||||||
|
use crate::task::{TaskOverview, TaskService};
|
||||||
use crate::theme::ActiveTheme;
|
use crate::theme::ActiveTheme;
|
||||||
use crate::view::sidebar::{Sidebar, TagItem};
|
use crate::view::sidebar::{Sidebar, TagItem};
|
||||||
use crate::view::status_bar::StatusBar;
|
use crate::view::status_bar::StatusBar;
|
||||||
@@ -10,6 +11,7 @@ pub(crate) struct App {
|
|||||||
sidebar: gpui::Entity<Sidebar>,
|
sidebar: gpui::Entity<Sidebar>,
|
||||||
filter_state: gpui::Entity<FilterState>,
|
filter_state: gpui::Entity<FilterState>,
|
||||||
status_bar: gpui::Entity<StatusBar>,
|
status_bar: gpui::Entity<StatusBar>,
|
||||||
|
task_service: TaskService,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl gpui::Render for App {
|
impl gpui::Render for App {
|
||||||
@@ -60,56 +62,83 @@ impl App {
|
|||||||
app.new(|cx: &mut gpui::Context<'_, App>| {
|
app.new(|cx: &mut gpui::Context<'_, App>| {
|
||||||
let filter_state = cx.new(|_cx| FilterState::new());
|
let filter_state = cx.new(|_cx| FilterState::new());
|
||||||
|
|
||||||
|
log::info!("Initializing TaskService");
|
||||||
|
let mut task_service = match TaskService::new() {
|
||||||
|
Ok(service) => {
|
||||||
|
log::info!("TaskService initialized successfully");
|
||||||
|
service
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to initialize TaskService: {:?}", e);
|
||||||
|
panic!("Cannot continue without TaskService");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
log::info!("Loading overview (tasks, projects, tags)");
|
||||||
|
let overview = task_service.get_overview().unwrap_or_else(|e| {
|
||||||
|
log::error!("Failed to get overview: {:?}", e);
|
||||||
|
TaskOverview {
|
||||||
|
tasks: vec![],
|
||||||
|
projects: vec![],
|
||||||
|
tags: vec![],
|
||||||
|
total_tasks: 0,
|
||||||
|
pending_tasks: 0,
|
||||||
|
completed_tasks: 0,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Loaded {} tasks, {} projects, {} tags",
|
||||||
|
overview.total_tasks,
|
||||||
|
overview.projects.len(),
|
||||||
|
overview.tags.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i, task) in overview.tasks.iter().take(5).enumerate() {
|
||||||
|
log::debug!(
|
||||||
|
"Task {}: {} (project: {:?}, tags: {:?})",
|
||||||
|
i + 1,
|
||||||
|
task.description,
|
||||||
|
task.project,
|
||||||
|
task.tags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if overview.tasks.len() > 5 {
|
||||||
|
log::debug!("... and {} more tasks", overview.tasks.len() - 5);
|
||||||
|
}
|
||||||
|
|
||||||
let mut project_tree = ProjectTree::new();
|
let mut project_tree = ProjectTree::new();
|
||||||
project_tree.build_from_projects(&[
|
project_tree.build_from_projects(&overview.projects);
|
||||||
("Work.Backend.API".to_string(), 5),
|
|
||||||
("Work.Backend.DB".to_string(), 7),
|
|
||||||
("Work.Frontend.React".to_string(), 3),
|
|
||||||
("Work.Frontend.Styling".to_string(), 2),
|
|
||||||
("Home.Kitchen".to_string(), 4),
|
|
||||||
("Home.Garden".to_string(), 2),
|
|
||||||
("ignis.v0.1.phase0".to_string(), 15),
|
|
||||||
("free-ai".to_string(), 2),
|
|
||||||
]);
|
|
||||||
|
|
||||||
project_tree.expand_path("Work");
|
let tags: Vec<TagItem> = overview
|
||||||
project_tree.expand_path("Work.Backend");
|
.tags
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, task_count)| {
|
||||||
|
log::debug!("Tag: {} ({} tasks)", name, task_count);
|
||||||
|
TagItem { name, task_count }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let tags = vec![
|
log::info!("Projects and tags loaded successfully");
|
||||||
TagItem {
|
|
||||||
name: "parser".to_string(),
|
|
||||||
task_count: 8,
|
|
||||||
},
|
|
||||||
TagItem {
|
|
||||||
name: "cli".to_string(),
|
|
||||||
task_count: 5,
|
|
||||||
},
|
|
||||||
TagItem {
|
|
||||||
name: "testing".to_string(),
|
|
||||||
task_count: 6,
|
|
||||||
},
|
|
||||||
TagItem {
|
|
||||||
name: "diagnostics".to_string(),
|
|
||||||
task_count: 7,
|
|
||||||
},
|
|
||||||
TagItem {
|
|
||||||
name: "analyzer".to_string(),
|
|
||||||
task_count: 3,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let status_bar = cx.new(|cx| StatusBar::new(cx));
|
let status_bar = cx.new(|cx| StatusBar::new(cx));
|
||||||
|
|
||||||
let sidebar = cx.new(|cx| {
|
let sidebar = cx.new(|cx| {
|
||||||
Sidebar::new(project_tree, tags, filter_state.clone(), cx)
|
Sidebar::new(project_tree, tags, filter_state.clone(), cx)
|
||||||
.on_filter_change(|filter, _window, _cx| {
|
.on_filter_change(|filter, _window, _cx| {
|
||||||
println!("Filter changed:");
|
log::info!("Filter changed");
|
||||||
if let Some(ref project) = filter.selected_project {
|
if let Some(ref project) = filter.selected_project {
|
||||||
println!(" Project: {}", project);
|
log::debug!("Selected project: {}", project);
|
||||||
} else {
|
} else {
|
||||||
println!(" Project: All");
|
log::debug!("Selected project: All");
|
||||||
|
}
|
||||||
|
if filter.active_tags.is_empty() {
|
||||||
|
log::debug!("Active tags: None");
|
||||||
|
} else {
|
||||||
|
let tags_list: Vec<&str> =
|
||||||
|
filter.active_tags.iter().map(|s| s.as_str()).collect();
|
||||||
|
log::debug!("Active tags: {}", tags_list.join(", "));
|
||||||
}
|
}
|
||||||
println!(" Active tags: {:?}", filter.active_tags);
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,6 +146,7 @@ impl App {
|
|||||||
sidebar,
|
sidebar,
|
||||||
filter_state,
|
filter_state,
|
||||||
status_bar,
|
status_bar,
|
||||||
|
task_service,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,5 +8,10 @@ mod theme;
|
|||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
env_logger::Builder::from_default_env()
|
||||||
|
.filter_level(log::LevelFilter::Debug)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
log::info!("Starting Task Warrior GPUI");
|
||||||
App::run();
|
App::run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum TaskError {
|
pub enum TaskError {
|
||||||
Storage(String),
|
Storage(String),
|
||||||
|
Config(String),
|
||||||
NotFound(uuid::Uuid),
|
NotFound(uuid::Uuid),
|
||||||
InvalidTag(String),
|
InvalidTag(String),
|
||||||
InvalidProject(String),
|
InvalidProject(String),
|
||||||
@@ -16,6 +18,7 @@ impl fmt::Display for TaskError {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
TaskError::Storage(msg) => write!(f, "Storage error: {}", msg),
|
TaskError::Storage(msg) => write!(f, "Storage error: {}", msg),
|
||||||
|
TaskError::Config(msg) => write!(f, "Configuration error: {}", msg),
|
||||||
TaskError::NotFound(id) => write!(f, "Task not found: {}", id),
|
TaskError::NotFound(id) => write!(f, "Task not found: {}", id),
|
||||||
TaskError::InvalidTag(tag) => write!(f, "Invalid tag: {}", tag),
|
TaskError::InvalidTag(tag) => write!(f, "Invalid tag: {}", tag),
|
||||||
TaskError::InvalidProject(project) => write!(f, "Invalid project: {}", project),
|
TaskError::InvalidProject(project) => write!(f, "Invalid project: {}", project),
|
||||||
|
|||||||
+1
-1
@@ -5,5 +5,5 @@ pub mod service;
|
|||||||
|
|
||||||
pub use error::{TaskError, TaskResult};
|
pub use error::{TaskError, TaskResult};
|
||||||
pub use filter::{DueDateFilter, TagsFilterMode, TaskFilter};
|
pub use filter::{DueDateFilter, TagsFilterMode, TaskFilter};
|
||||||
pub use model::{Task, TaskAnnotation, TaskPriority, TaskStatus, TaskUpdate};
|
pub use model::{Task, TaskAnnotation, TaskOverview, TaskPriority, TaskStatus, TaskUpdate};
|
||||||
pub use service::{SyncResult, TaskService};
|
pub use service::{SyncResult, TaskService};
|
||||||
|
|||||||
@@ -214,3 +214,13 @@ pub struct TaskUpdate {
|
|||||||
pub annotations: Option<Vec<String>>,
|
pub annotations: Option<Vec<String>>,
|
||||||
pub dependencies: Option<Vec<String>>,
|
pub dependencies: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TaskOverview {
|
||||||
|
pub tasks: Vec<Task>,
|
||||||
|
pub projects: Vec<(String, usize)>,
|
||||||
|
pub tags: Vec<(String, usize)>,
|
||||||
|
pub total_tasks: usize,
|
||||||
|
pub pending_tasks: usize,
|
||||||
|
pub completed_tasks: usize,
|
||||||
|
}
|
||||||
|
|||||||
+163
-16
@@ -9,34 +9,85 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use super::error::{TaskError, TaskResult};
|
use super::error::{TaskError, TaskResult};
|
||||||
use super::filter::TaskFilter;
|
use super::filter::TaskFilter;
|
||||||
use super::model::{Task, TaskPriority, TaskStatus};
|
use super::model::{Task, TaskOverview, TaskStatus};
|
||||||
|
|
||||||
pub struct TaskService {
|
pub struct TaskService {
|
||||||
replica: Replica,
|
replica: Replica,
|
||||||
taskdb_dir: PathBuf,
|
taskdb_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_taskrc_config() -> TaskResult<PathBuf> {
|
||||||
|
if let Ok(taskdata) = std::env::var("TASKDATA") {
|
||||||
|
log::info!("Using TASKDATA env var: {}", taskdata);
|
||||||
|
return Ok(PathBuf::from(taskdata));
|
||||||
|
}
|
||||||
|
|
||||||
|
let taskrc_path = if let Ok(taskrc) = std::env::var("TASKRC") {
|
||||||
|
PathBuf::from(taskrc)
|
||||||
|
} else {
|
||||||
|
dirs::home_dir()
|
||||||
|
.ok_or_else(|| TaskError::Config("Cannot find home directory".into()))?
|
||||||
|
.join(".config/task/taskrc")
|
||||||
|
};
|
||||||
|
|
||||||
|
log::debug!("Looking for taskrc at: {:?}", taskrc_path);
|
||||||
|
|
||||||
|
if taskrc_path.exists() {
|
||||||
|
let content = std::fs::read_to_string(&taskrc_path)
|
||||||
|
.map_err(|e| TaskError::Config(format!("Failed to read taskrc: {}", e)))?;
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.starts_with("data.location=") {
|
||||||
|
let path = line.strip_prefix("data.location=").unwrap().trim();
|
||||||
|
|
||||||
|
let expanded = if path.starts_with("~") {
|
||||||
|
let home = dirs::home_dir()
|
||||||
|
.ok_or_else(|| TaskError::Config("Cannot expand ~ in path".into()))?;
|
||||||
|
PathBuf::from(path.replacen("~", home.to_str().unwrap(), 1))
|
||||||
|
} else {
|
||||||
|
PathBuf::from(path)
|
||||||
|
};
|
||||||
|
|
||||||
|
log::info!("Found data.location in taskrc: {:?}", expanded);
|
||||||
|
return Ok(expanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::warn!("No taskrc found or data.location not set, using default ~/.task");
|
||||||
|
let default = dirs::home_dir()
|
||||||
|
.ok_or_else(|| TaskError::Config("Cannot find home directory".into()))?
|
||||||
|
.join(".task");
|
||||||
|
Ok(default)
|
||||||
|
}
|
||||||
|
|
||||||
impl TaskService {
|
impl TaskService {
|
||||||
pub fn new() -> TaskResult<Self> {
|
pub fn new() -> TaskResult<Self> {
|
||||||
let taskdb_dir = dirs::home_dir()
|
let taskdb_dir = read_taskrc_config()?;
|
||||||
.ok_or_else(|| TaskError::Storage("Cannot find home directory".into()))?
|
|
||||||
.join(".task");
|
log::debug!("TaskService: Using taskdb_dir: {:?}", taskdb_dir);
|
||||||
|
log::debug!("TaskService: Directory exists: {}", taskdb_dir.exists());
|
||||||
|
|
||||||
Self::with_path(taskdb_dir)
|
Self::with_path(taskdb_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_path(taskdb_dir: PathBuf) -> TaskResult<Self> {
|
pub fn with_path(taskdb_dir: PathBuf) -> TaskResult<Self> {
|
||||||
|
log::debug!("TaskService: Initializing with path: {:?}", taskdb_dir);
|
||||||
|
|
||||||
let storage = StorageConfig::OnDisk {
|
let storage = StorageConfig::OnDisk {
|
||||||
taskdb_dir: taskdb_dir.clone(),
|
taskdb_dir: taskdb_dir.clone(),
|
||||||
create_if_missing: true,
|
create_if_missing: true,
|
||||||
access_mode: AccessMode::ReadWrite,
|
access_mode: AccessMode::ReadWrite,
|
||||||
};
|
};
|
||||||
|
|
||||||
let replica = Replica::new(
|
log::debug!("TaskService: Creating storage...");
|
||||||
storage
|
let replica = Replica::new(storage.into_storage().map_err(|e| {
|
||||||
.into_storage()
|
log::error!("TaskService: Storage creation failed: {}", e);
|
||||||
.map_err(|e| TaskError::Storage(e.to_string()))?,
|
TaskError::Storage(e.to_string())
|
||||||
);
|
})?);
|
||||||
|
|
||||||
|
log::debug!("TaskService: Replica created successfully");
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
replica,
|
replica,
|
||||||
@@ -92,10 +143,17 @@ impl TaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_tasks(&mut self) -> TaskResult<Vec<Task>> {
|
pub fn get_all_tasks(&mut self) -> TaskResult<Vec<Task>> {
|
||||||
let all = self
|
log::debug!("TaskService::get_all_tasks: Fetching all tasks from replica");
|
||||||
.replica
|
let all = self.replica.all_tasks().map_err(|e| {
|
||||||
.all_tasks()
|
log::error!("TaskService::get_all_tasks: Failed to get all tasks: {}", e);
|
||||||
.map_err(|e| TaskError::Storage(e.to_string()))?;
|
TaskError::Storage(e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"TaskService::get_all_tasks: Found {} tasks in storage",
|
||||||
|
all.len()
|
||||||
|
);
|
||||||
|
|
||||||
let working_set = self
|
let working_set = self
|
||||||
.replica
|
.replica
|
||||||
.working_set()
|
.working_set()
|
||||||
@@ -110,9 +168,70 @@ impl TaskService {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"TaskService::get_all_tasks: Converted to {} Task objects",
|
||||||
|
tasks.len()
|
||||||
|
);
|
||||||
Ok(tasks)
|
Ok(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_overview(&mut self) -> TaskResult<TaskOverview> {
|
||||||
|
log::info!("TaskService::get_overview: Fetching complete overview");
|
||||||
|
|
||||||
|
let tasks = self.get_all_tasks()?;
|
||||||
|
|
||||||
|
log::debug!("Processing {} tasks for overview", tasks.len());
|
||||||
|
|
||||||
|
let mut project_counts: HashMap<String, usize> = HashMap::new();
|
||||||
|
let mut tag_counts: HashMap<String, usize> = HashMap::new();
|
||||||
|
let mut pending_count = 0;
|
||||||
|
let mut completed_count = 0;
|
||||||
|
|
||||||
|
for task in &tasks {
|
||||||
|
match task.status {
|
||||||
|
TaskStatus::Pending => pending_count += 1,
|
||||||
|
TaskStatus::Completed => completed_count += 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(task.status, TaskStatus::Pending) {
|
||||||
|
if let Some(project) = &task.project {
|
||||||
|
*project_counts.entry(project.clone()).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for tag in &task.tags {
|
||||||
|
*tag_counts.entry(tag.clone()).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut projects: Vec<(String, usize)> = project_counts.into_iter().collect();
|
||||||
|
projects.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
|
||||||
|
|
||||||
|
let mut tags: Vec<(String, usize)> = tag_counts.into_iter().collect();
|
||||||
|
tags.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
|
||||||
|
|
||||||
|
let total_tasks = tasks.len();
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Overview: {} tasks ({} pending, {} completed), {} projects, {} tags",
|
||||||
|
total_tasks,
|
||||||
|
pending_count,
|
||||||
|
completed_count,
|
||||||
|
projects.len(),
|
||||||
|
tags.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(TaskOverview {
|
||||||
|
tasks,
|
||||||
|
projects,
|
||||||
|
tags,
|
||||||
|
total_tasks,
|
||||||
|
pending_tasks: pending_count,
|
||||||
|
completed_tasks: completed_count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_filtered_tasks(&mut self, filter: &TaskFilter) -> TaskResult<Vec<Task>> {
|
pub fn get_filtered_tasks(&mut self, filter: &TaskFilter) -> TaskResult<Vec<Task>> {
|
||||||
let all = self.get_all_tasks()?;
|
let all = self.get_all_tasks()?;
|
||||||
Ok(filter.apply(&all))
|
Ok(filter.apply(&all))
|
||||||
@@ -338,7 +457,13 @@ impl TaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_tags(&mut self) -> TaskResult<Vec<(String, usize)>> {
|
pub fn list_tags(&mut self) -> TaskResult<Vec<(String, usize)>> {
|
||||||
|
log::debug!("TaskService::list_tags: Getting all tasks");
|
||||||
let all_tasks = self.get_all_tasks()?;
|
let all_tasks = self.get_all_tasks()?;
|
||||||
|
log::debug!(
|
||||||
|
"TaskService::list_tags: Processing {} tasks for tags",
|
||||||
|
all_tasks.len()
|
||||||
|
);
|
||||||
|
|
||||||
let mut tag_counts: HashMap<String, usize> = HashMap::new();
|
let mut tag_counts: HashMap<String, usize> = HashMap::new();
|
||||||
|
|
||||||
for task in all_tasks {
|
for task in all_tasks {
|
||||||
@@ -349,6 +474,11 @@ impl TaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"TaskService::list_tags: Found {} unique tags",
|
||||||
|
tag_counts.len()
|
||||||
|
);
|
||||||
|
|
||||||
let mut stats: Vec<(String, usize)> = tag_counts.into_iter().collect();
|
let mut stats: Vec<(String, usize)> = tag_counts.into_iter().collect();
|
||||||
|
|
||||||
stats.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
|
stats.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
|
||||||
@@ -357,8 +487,14 @@ impl TaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_projects(&mut self) -> TaskResult<Vec<(String, usize, usize)>> {
|
pub fn list_projects(&mut self) -> TaskResult<Vec<(String, usize, usize)>> {
|
||||||
|
log::debug!("TaskService::list_projects: Getting all tasks");
|
||||||
let all_tasks = self.get_all_tasks()?;
|
let all_tasks = self.get_all_tasks()?;
|
||||||
let mut project_counts: HashMap<String, (usize, usize)> = HashMap::new(); // (total, pending)
|
log::debug!(
|
||||||
|
"TaskService::list_projects: Processing {} tasks for projects",
|
||||||
|
all_tasks.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut project_counts: HashMap<String, (usize, usize)> = HashMap::new();
|
||||||
|
|
||||||
for task in all_tasks {
|
for task in all_tasks {
|
||||||
if let Some(project) = &task.project {
|
if let Some(project) = &task.project {
|
||||||
@@ -370,6 +506,11 @@ impl TaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"TaskService::list_projects: Found {} unique projects",
|
||||||
|
project_counts.len()
|
||||||
|
);
|
||||||
|
|
||||||
let stats: Vec<(String, usize, usize)> = project_counts
|
let stats: Vec<(String, usize, usize)> = project_counts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, (task_count, pending_count))| (name, task_count, pending_count))
|
.map(|(name, (task_count, pending_count))| (name, task_count, pending_count))
|
||||||
@@ -379,11 +520,17 @@ impl TaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_projects_for_tree(&mut self) -> TaskResult<Vec<(String, usize)>> {
|
pub fn get_projects_for_tree(&mut self) -> TaskResult<Vec<(String, usize)>> {
|
||||||
|
log::debug!("TaskService::get_projects_for_tree: Getting project stats");
|
||||||
let stats = self.list_projects()?;
|
let stats = self.list_projects()?;
|
||||||
Ok(stats
|
let result: Vec<(String, usize)> = stats
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, _total, pending)| (name, pending))
|
.map(|(name, _total, pending)| (name, pending))
|
||||||
.collect())
|
.collect();
|
||||||
|
log::debug!(
|
||||||
|
"TaskService::get_projects_for_tree: Returning {} projects",
|
||||||
|
result.len()
|
||||||
|
);
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_annotation(&mut self, uuid: Uuid, description: String) -> TaskResult<Task> {
|
pub fn add_annotation(&mut self, uuid: Uuid, description: String) -> TaskResult<Task> {
|
||||||
|
|||||||
+56
-39
@@ -288,57 +288,74 @@ impl Render for Sidebar {
|
|||||||
.bg(theme.background)
|
.bg(theme.background)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.px_3()
|
.flex()
|
||||||
.py_2()
|
.flex_col()
|
||||||
.border_b_1()
|
.h(gpui::relative(0.5))
|
||||||
.border_color(theme.border)
|
.overflow_hidden()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_sm()
|
.px_3()
|
||||||
.font_weight(gpui::FontWeight::BOLD)
|
.py_2()
|
||||||
.text_color(theme.foreground)
|
.border_b_1()
|
||||||
.child("PROJECTS"),
|
.border_color(theme.border)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.font_weight(gpui::FontWeight::BOLD)
|
||||||
|
.text_color(theme.foreground)
|
||||||
|
.child("PROJECTS"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.id("sidebar-projects")
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.flex_1()
|
||||||
|
.min_h_0()
|
||||||
|
.py_2()
|
||||||
|
.overflow_y_scroll()
|
||||||
|
.scrollbar_width(gpui::px(6.0))
|
||||||
|
.children(projects),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id("sidebar-projects")
|
.h_px()
|
||||||
.flex()
|
.bg(theme.border),
|
||||||
.flex_col()
|
|
||||||
.flex_1()
|
|
||||||
.py_2()
|
|
||||||
.overflow_y_scroll()
|
|
||||||
.scrollbar_width(gpui::px(6.0))
|
|
||||||
.children(projects),
|
|
||||||
)
|
)
|
||||||
.child(div().px_3().py_2().child(Divider::new(
|
|
||||||
theme.border,
|
|
||||||
crate::components::divider::DividerDirection::Horizontal,
|
|
||||||
)))
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.px_3()
|
.flex()
|
||||||
.py_2()
|
.flex_col()
|
||||||
.border_b_1()
|
.h(gpui::relative(0.5))
|
||||||
.border_color(theme.border)
|
.overflow_hidden()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_sm()
|
.px_3()
|
||||||
.font_weight(gpui::FontWeight::BOLD)
|
.py_2()
|
||||||
.text_color(theme.foreground)
|
.border_b_1()
|
||||||
.child("TAGS"),
|
.border_color(theme.border)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.font_weight(gpui::FontWeight::BOLD)
|
||||||
|
.text_color(theme.foreground)
|
||||||
|
.child("TAGS"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.id("sidebar-tags")
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.flex_1()
|
||||||
|
.min_h_0()
|
||||||
|
.py_2()
|
||||||
|
.overflow_y_scroll()
|
||||||
|
.scrollbar_width(gpui::px(6.0))
|
||||||
|
.children(tags),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.id("sidebar-tags")
|
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.flex_1()
|
|
||||||
.py_2()
|
|
||||||
.overflow_y_scroll()
|
|
||||||
.scrollbar_width(gpui::px(6.0))
|
|
||||||
.children(tags),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user