Skip to main content

things3_cloud/commands/
area.rs

1use crate::app::Cli;
2use crate::commands::Command;
3use crate::common::{
4    colored, fmt_project_with_note, fmt_task_line, fmt_task_with_note, BOLD, DIM, ICONS, MAGENTA,
5};
6use crate::wire::task::TaskStatus;
7use anyhow::Result;
8use clap::Args;
9
10#[derive(Debug, Args)]
11#[command(about = "Show projects and tasks in an area")]
12pub struct AreaArgs {
13    /// Area UUID (or unique UUID prefix)
14    pub area_id: String,
15    /// Show notes beneath each task/project
16    #[arg(long)]
17    pub detailed: bool,
18    /// Include completed and canceled items
19    #[arg(long)]
20    pub all: bool,
21}
22
23impl Command for AreaArgs {
24    fn run_with_ctx(
25        &self,
26        cli: &Cli,
27        out: &mut dyn std::io::Write,
28        ctx: &mut dyn crate::cmd_ctx::CmdCtx,
29    ) -> Result<()> {
30        let store = cli.load_store()?;
31        let today = ctx.today();
32        let (area_opt, err, ambiguous) = store.resolve_area_identifier(&self.area_id);
33        let Some(area) = area_opt else {
34            eprintln!("{err}");
35            for match_area in ambiguous {
36                eprintln!(
37                    "  {} {}  ({})",
38                    ICONS.area, match_area.title, match_area.uuid
39                );
40            }
41            return Ok(());
42        };
43
44        let status_filter = if self.all {
45            None
46        } else {
47            Some(TaskStatus::Incomplete)
48        };
49        let mut projects = store
50            .projects(status_filter)
51            .into_iter()
52            .filter(|p| p.area.as_ref() == Some(&area.uuid))
53            .collect::<Vec<_>>();
54        projects.sort_by_key(|p| p.index);
55
56        let mut loose_tasks = store
57            .tasks(status_filter, Some(false), None)
58            .into_iter()
59            .filter(|t| {
60                t.area.as_ref() == Some(&area.uuid)
61                    && !t.is_project()
62                    && store.effective_project_uuid(t).is_none()
63            })
64            .collect::<Vec<_>>();
65        loose_tasks.sort_by_key(|t| t.index);
66
67        let project_count = projects.len();
68        let task_count = loose_tasks.len();
69
70        let tags = if area.tags.is_empty() {
71            String::new()
72        } else {
73            let names = area
74                .tags
75                .iter()
76                .map(|t| store.resolve_tag_title(t))
77                .collect::<Vec<_>>()
78                .join(", ");
79            colored(&format!(" [{names}]"), &[DIM], cli.no_color)
80        };
81
82        let mut parts = Vec::new();
83        if project_count > 0 {
84            parts.push(format!(
85                "{} project{}",
86                project_count,
87                if project_count == 1 { "" } else { "s" }
88            ));
89        }
90        if task_count > 0 {
91            parts.push(format!(
92                "{} task{}",
93                task_count,
94                if task_count == 1 { "" } else { "s" }
95            ));
96        }
97        let count_str = if parts.is_empty() {
98            String::new()
99        } else {
100            format!("  ({})", parts.join(", "))
101        };
102
103        writeln!(
104            out,
105            "{}{}",
106            colored(
107                &format!("{} {}{}", ICONS.area, area.title, count_str),
108                &[BOLD, MAGENTA],
109                cli.no_color,
110            ),
111            tags
112        )?;
113
114        let mut all_uuids = vec![area.uuid.clone()];
115        all_uuids.extend(projects.iter().map(|p| p.uuid.clone()));
116        all_uuids.extend(loose_tasks.iter().map(|t| t.uuid.clone()));
117        let id_prefix_len = store.unique_prefix_length(&all_uuids);
118
119        if !loose_tasks.is_empty() {
120            writeln!(out)?;
121            for task in loose_tasks {
122                let line = fmt_task_line(
123                    &task,
124                    &store,
125                    false,
126                    true,
127                    false,
128                    Some(id_prefix_len),
129                    &today,
130                    cli.no_color,
131                );
132                writeln!(
133                    out,
134                    "{}",
135                    fmt_task_with_note(
136                        line,
137                        &task,
138                        "  ",
139                        Some(id_prefix_len),
140                        self.detailed,
141                        cli.no_color,
142                    )
143                )?;
144            }
145        }
146
147        if !projects.is_empty() {
148            writeln!(out)?;
149            for project in projects {
150                writeln!(
151                    out,
152                    "{}",
153                    fmt_project_with_note(
154                        &project,
155                        &store,
156                        "  ",
157                        Some(id_prefix_len),
158                        true,
159                        false,
160                        self.detailed,
161                        &today,
162                        cli.no_color,
163                    )
164                )?;
165            }
166        }
167
168        Ok(())
169    }
170}