1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use std::io;

use radicle_term::table::TableOptions;
use radicle_term::{Table, VStack};

use radicle::cob;
use radicle::cob::issue;
use radicle::cob::issue::CloseReason;
use radicle::Profile;

use crate::terminal as term;
use crate::terminal::format::Author;
use crate::terminal::Element;

pub const OPEN_MSG: &str = r#"
<!--
Please enter an issue title and description.

The first line is the issue title. The issue description
follows, and must be separated by a blank line, just
like a commit message. Markdown is supported in the title
and description.
-->
"#;

/// Display format.
#[derive(Default, Debug, PartialEq, Eq)]
pub enum Format {
    #[default]
    Full,
    Header,
}

pub fn get_title_description(
    title: Option<String>,
    description: Option<String>,
) -> io::Result<Option<(String, String)>> {
    term::patch::Message::edit_title_description(title, description, OPEN_MSG)
}

pub fn show(
    issue: &issue::Issue,
    id: &cob::ObjectId,
    format: Format,
    profile: &Profile,
) -> anyhow::Result<()> {
    let labels: Vec<String> = issue.labels().cloned().map(|t| t.into()).collect();
    let assignees: Vec<String> = issue
        .assignees()
        .map(|a| term::format::did(a).to_string())
        .collect();
    let author = issue.author();
    let did = author.id();
    let author = Author::new(did, profile);

    let mut attrs = Table::<2, term::Line>::new(TableOptions {
        spacing: 2,
        ..TableOptions::default()
    });

    attrs.push([
        term::format::tertiary("Title".to_owned()).into(),
        term::format::bold(issue.title().to_owned()).into(),
    ]);

    attrs.push([
        term::format::tertiary("Issue".to_owned()).into(),
        term::format::bold(id.to_string()).into(),
    ]);

    attrs.push([
        term::format::tertiary("Author".to_owned()).into(),
        author.line(),
    ]);

    if !labels.is_empty() {
        attrs.push([
            term::format::tertiary("Labels".to_owned()).into(),
            term::format::secondary(labels.join(", ")).into(),
        ]);
    }

    if !assignees.is_empty() {
        attrs.push([
            term::format::tertiary("Assignees".to_owned()).into(),
            term::format::dim(assignees.join(", ")).into(),
        ]);
    }

    attrs.push([
        term::format::tertiary("Status".to_owned()).into(),
        match issue.state() {
            issue::State::Open => term::format::positive("open".to_owned()).into(),
            issue::State::Closed {
                reason: CloseReason::Solved,
            } => term::Line::spaced([
                term::format::negative("closed").into(),
                term::format::negative("(solved)").italic().dim().into(),
            ]),
            issue::State::Closed {
                reason: CloseReason::Other,
            } => term::Line::spaced([term::format::negative("closed").into()]),
        },
    ]);

    let description = issue.description();
    let mut widget = VStack::default()
        .border(Some(term::colors::FAINT))
        .child(attrs)
        .children(if !description.is_empty() {
            vec![
                term::Label::blank().boxed(),
                term::textarea(description.trim()).wrap(60).boxed(),
            ]
        } else {
            vec![]
        });

    if format == Format::Full {
        for (id, comment) in issue.replies() {
            let hstack = term::comment::header(id, comment, profile);

            widget = widget.divider();
            widget.push(hstack);
            widget.push(term::textarea(comment.body()).wrap(60));
        }
    }
    widget.print();

    Ok(())
}