quinjet 0.0.50

A fast, live, keyboard-first Git source-control interface for the terminal
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use super::*;

impl App {
    pub(crate) fn tick(&mut self, now: Instant) -> (Vec<AppEffect>, bool) {
        let toast_expired = self
            .toast
            .as_ref()
            .is_some_and(|toast| now >= toast.expires_at);
        let mut changed = toast_expired;
        if toast_expired {
            self.toast = None;
        }
        if self
            .pending_g
            .is_some_and(|pressed| now.duration_since(pressed) >= Duration::from_millis(500))
        {
            self.pending_g = None;
        }

        let mut effects = Vec::new();
        if self.preview_due.is_some_and(|due| now >= due) {
            self.preview_due = None;
            self.request_preview(&mut effects);
            changed = true;
        }
        if self.pull_request_poll_due.is_some_and(|due| now >= due) {
            self.refresh_pull_request_live(now, false, &mut effects);
            changed = true;
        }
        if self.busy.is_some() {
            self.operation_frame = self.operation_frame.wrapping_add(1) % OPERATION_SPINNER.len();
            changed = true;
        }
        (effects, changed)
    }

    pub(crate) fn operation_spinner(&self) -> &'static str {
        OPERATION_SPINNER
            .get(self.operation_frame % OPERATION_SPINNER.len())
            .copied()
            .unwrap_or("")
    }

    #[doc = " A GitHub webhook was forwarded to this session. The payload is only a"]
    #[doc = " hint that something changed, so the poller runs immediately rather than"]
    #[doc = " trying to apply the delivery itself."]
    pub(crate) fn webhook_delivered(&mut self, now: Instant) -> Vec<AppEffect> {
        let mut effects = Vec::new();
        if self.pull_request.is_some() {
            self.refresh_pull_request_live(now, true, &mut effects);
        }
        effects
    }

    #[doc = " Whether any pull-request read is on its way. The view shows one status"]
    #[doc = " label for all of them, because the reader cares that it is refreshing,"]
    #[doc = " not which of four endpoints is answering."]
    pub(crate) const fn pull_request_refreshing(&self) -> bool {
        self.pull_request_loading
            || self.pull_request_checks_loading
            || self.pull_request_conversation_loading
            || self.pull_request_review_loading
            || self.pull_request_check_log_loading
    }

    #[doc = " Whether the pull request itself was answered from disk rather than the"]
    #[doc = " network. Check state is deliberately held for only thirty seconds, so"]
    #[doc = " including it here made the answer almost always false and the label"]
    #[doc = " never appeared at all."]
    pub(crate) const fn pull_request_served_from_cache(&self) -> bool {
        self.pull_request.is_some()
            && self.pull_request_from_cache
            && self.pull_request_conversation.from_cache
            && !self.pull_request_refreshing()
    }

    #[doc = " Watch a running pull request closely and a settled one loosely. The"]
    #[doc = " interval also stretches when the reader is somewhere else, so a loaded"]
    #[doc = " pull request stays fresh without spending requests on an unseen pane."]
    pub(super) fn pull_request_poll_interval(&self) -> Duration {
        if !self.tab_active || self.view != View::PullRequests {
            return PULL_REQUEST_BACKGROUND_POLL;
        }
        if self
            .pull_request_checks
            .iter()
            .any(|check| check.status.is_running())
        {
            PULL_REQUEST_ACTIVE_POLL
        } else {
            PULL_REQUEST_IDLE_POLL
        }
    }

    pub(super) fn schedule_pull_request_poll(&mut self, now: Instant) {
        self.pull_request_poll_due = self
            .pull_request
            .is_some()
            .then(|| now + self.pull_request_poll_interval());
    }

    #[doc = " Run whichever live reads are due. `force` is a webhook delivery saying"]
    #[doc = " something definitely changed, so every stream reads at once."]
    #[doc = ""]
    #[doc = " Each read is independent, so a single failing endpoint never stalls the"]
    #[doc = " others, and every one of them coalesces if a previous poll is still in"]
    #[doc = " flight."]
    pub(super) fn refresh_pull_request_live(
        &mut self,
        now: Instant,
        force: bool,
        effects: &mut Vec<AppEffect>,
    ) {
        self.schedule_pull_request_poll(now);
        let Some(number) = self.pull_request_exact_number else {
            return;
        };
        if self.pull_request.is_none() {
            return;
        }
        let due = |last: Option<Instant>, interval: Duration| {
            force || last.is_none_or(|last| now.duration_since(last) >= interval)
        };

        if due(
            self.pull_request_checks_read_at,
            self.pull_request_poll_interval(),
        ) {
            let issued = effects.len();
            self.request_pull_request_checks(true, effects);
            if effects.len() > issued {
                self.pull_request_checks_read_at = Some(now);
            }
        }
        let settled = self
            .pull_request
            .as_ref()
            .is_some_and(|pull_request| matches!(pull_request.state.as_str(), "MERGED" | "CLOSED"));
        if settled && !force {
            return;
        }
        if due(self.pull_request_detail_read_at, PULL_REQUEST_DETAIL_POLL) {
            let issued = effects.len();
            self.request_pull_request_lookup(number, true, true, effects);
            self.request_pull_request_conversation(true, effects);
            self.request_pull_request_review(true, effects);
            if effects.len() > issued {
                self.pull_request_detail_read_at = Some(now);
            }
        }
        let running = self
            .selected_pull_request_check()
            .is_some_and(|check| check.status.is_running());
        if running && due(self.pull_request_log_read_at, PULL_REQUEST_LOG_POLL) {
            let issued = effects.len();
            self.request_check_run_log(true, effects);
            if effects.len() > issued {
                self.pull_request_log_read_at = Some(now);
            }
        }
    }

    pub(crate) fn filesystem_changed(&mut self, effects: &mut Vec<AppEffect>) {
        self.changes_diff_version = self.changes_diff_version.wrapping_add(1);
        if self.view == View::Changes && self.auxiliary_preview.is_none() {
            self.invalidate_preview();
            self.local_diff_loading_path = None;
            self.local_diff_pending_paths.clear();
        }
        self.request_refresh(effects);
    }

    #[doc = " The repository heartbeat. Pull-request liveness is separate because it"]
    #[doc = " paces itself against GitHub rather than the local working tree."]
    pub(crate) fn periodic_refresh(&mut self, effects: &mut Vec<AppEffect>) {
        self.request_refresh(effects);
    }
}