ghtui 0.2.0

A keyboard-driven terminal client for GitHub, with a pull request diff viewer that goes further than GitHub's
//! Writing: star and comment.

use super::*;
use crate::route::{code, discussions, issues, pulls};

#[must_use]
pub fn star(state: &mut State) -> Vec<Cmd> {
    let Some(repo) = state.route().and_then(Route::repo).cloned() else {
        return state.just_say("Open a repository to star it");
    };
    let Some(overview) = state.overview(&repo) else {
        return state.just_say("The repository hasn't loaded yet");
    };
    let (id, starred) = (overview.id.clone(), !overview.starred);
    set_starred(state, &repo, starred);
    let star = Change::Star { repo, id, starred };
    vec![crate::act::send(star, crate::act::By::Star)]
}

/// Shows a repository as starred or not (optimistically, before GitHub
/// confirms).
pub fn set_starred(state: &mut State, repo: &RepoId, starred: bool) {
    if let Some(remote) = state.data.get_mut(&DataKey::Repo(repo.clone()))
        && let Some(Data::Repo(o)) = &mut remote.data
        && o.starred != starred
    {
        o.starred = starred;
        o.summary.stars = if starred {
            o.summary.stars + 1
        } else {
            o.summary.stars.saturating_sub(1)
        };
        remote.changed();
    }
}

/// `>`: a reply quoting the selected comment, under it.
#[must_use]
pub(super) fn quote_reply(state: &mut State) -> Vec<Cmd> {
    let Screen::Page(p) = state.screen() else {
        return Vec::new();
    };
    match p.selected_link().cloned() {
        Some(Link::Quote { author, body, at }) => {
            let quoted: String = body.trim().lines().map(|l| format!("> {l}\n")).collect();
            comment_with(state, &format!("> @{author} wrote:\n{quoted}\n"), Some(at))
        }
        _ => {
            let down = state.first_key(Action::Down);
            state.just_say(format!(
                "Select a comment to quote: {down} goes comment by comment"
            ))
        }
    }
}

/// Opens the composer on the issue or pull request on screen, starting
/// with `text` (quoting the comment made `quoting`).
#[must_use]
pub(super) fn comment_with(state: &mut State, text: &str, quoting: Option<String>) -> Vec<Cmd> {
    let target = match state.route() {
        Some(Route::Issues(issues::Issue { repo, number })) => {
            let key = DataKey::Issue(repo.clone(), *number);
            match state.get(&key) {
                Some(Data::Issue(Some(issue))) => {
                    Some((issue.id.clone(), format!("{repo}#{number}")))
                }
                _ => None,
            }
        }
        Some(Route::Pulls(pulls::Pr { pr, .. })) => {
            state.activity(pr).map(|a| (a.id.clone(), pr.to_string()))
        }
        Some(Route::Discussions(discussions::Discussion { .. })) => {
            return crate::discuss::act(state, Action::Comment);
        }
        // A commit's comments.
        Some(Route::Code(code::Commit { repo, oid, .. })) => {
            let target = compose::Target::Commit {
                repo: repo.clone(),
                oid: oid.clone(),
            };
            return state.compose(target, text);
        }
        _ => {
            return state.just_say("Comments go on issues and pull requests");
        }
    };
    let Some((subject_id, name)) = target else {
        return state.just_say("Still loading");
    };
    let target = compose::Target::Conversation {
        subject_id,
        name,
        quoting,
    };
    state.compose(target, text)
}