quinjet 0.0.72

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 {
    #[doc = " Drop only the prepared diff. The section, cursors, checks and"]
    #[doc = " conversation stay exactly where the reader left them."]
    pub(super) fn reset_pull_request_diff_runtime(&mut self) {
        self.pull_request_workspace_generation = None;
        self.pull_request_diff_source = None;
        self.pull_request_documents.clear();
        self.pull_request_document_order.clear();
        self.pull_request_document_bytes = 0;
        self.pull_request_viewport_paths.clear();
        self.pull_request_scroll_anchor = None;
        self.pull_request_restore_scroll = false;
        self.pull_request_prefetch_paths.clear();
        self.pull_request_loading_path = None;
        self.pull_request_single_file = None;
        self.pull_request_prefetching = false;
        self.pull_request_prefetch_retrying = false;
    }

    pub(super) fn reset_pull_request_runtime(&mut self) {
        self.reset_pull_request_diff_runtime();
        self.reset_pull_request_review();
        self.pull_request_stack = None;
        self.pull_request_stack_loading = false;
        self.pull_request_stack_error = None;
        self.pull_request_stack_anchor = None;
        self.pull_request_stack_cursor = None;
        self.stack_inspector.clear();
        self.pull_request_section = PullRequestSection::Overview;
        self.pull_request_file_view = PullRequestFileView::AllFiles;
        self.pull_request_files.clear();
        self.pull_request_tree.clear();
        self.pull_request_total_files = 0;
        self.pull_request_files_truncated = false;
        self.pull_request_file_cursor = 0;
        self.pull_request_tree_cursor = 0;
        self.collapsed_pull_request_directories.clear();
        self.pull_request_checks.clear();
        self.pull_request_check_cursor = None;
        self.selected_check_section = None;
        self.collapsed_check_sections.clear();
        self.pull_request_checks_loading = false;
        self.pull_request_checks_error = None;
        self.pull_request_checks_generation = self.pull_request_checks_generation.wrapping_add(1);
        self.pull_request_prefetched_logs.clear();
        self.pull_request_conversation = PullRequestConversation::default();
        self.pull_request_conversation_loading = false;
        self.pull_request_conversation_refresh_again = false;
        self.pull_request_conversation_error = None;
        self.pull_request_conversation_generation =
            self.pull_request_conversation_generation.wrapping_add(1);
        self.pull_request_check_log = None;
        self.pull_request_check_log_loading = false;
        self.pull_request_check_log_error = None;
        self.pull_request_check_log_target = None;
        self.pull_request_check_log_generation =
            self.pull_request_check_log_generation.wrapping_add(1);
        self.expanded_check_steps.clear();
        self.pull_request_step_cursor = 0;
        self.pull_request_content_rows.clear();
        self.pull_request_content_rows_key = None;
        self.pull_request_content_width = 0;
        self.pull_request_content_links.clear();
        self.pull_request_content_generation = self.pull_request_content_generation.wrapping_add(1);
        self.pull_request_checks_read_at = None;
        self.pull_request_detail_read_at = None;
        self.pull_request_log_read_at = None;
        self.reset_view_sidebar_scroll(View::PullRequests);
    }

    pub(super) fn apply_pull_request_index(&mut self, index: PullRequestDiffIndex) {
        self.pull_request_file_view = PullRequestFileView::AllFiles;
        self.pull_request_documents.clear();
        self.pull_request_document_order.clear();
        self.pull_request_document_bytes = 0;
        self.pull_request_viewport_paths.clear();
        self.pull_request_prefetch_paths.clear();
        self.pull_request_scroll_anchor = None;
        self.pull_request_restore_scroll = false;
        self.pull_request_loading_path = None;
        self.pull_request_single_file = None;
        self.pull_request_prefetching = false;
        self.pull_request_prefetch_retrying = false;
        self.pull_request_files = index.files;
        self.pull_request_total_files = index.total_files;
        self.pull_request_files_truncated = index.truncated;
        self.pull_request_file_cursor = self
            .pull_request_file_cursor
            .min(self.pull_request_files.len().saturating_sub(1));
        self.sync_pull_request_tree_cursor_to_file();
        let paths = self
            .pull_request_files
            .iter()
            .map(|file| file.path.clone())
            .collect::<Vec<_>>();
        self.reset_preview_file_folds(&paths);
        self.selected_preview_file = self
            .selected_pull_request_file()
            .map(|file| file.path.clone());
        self.preview_file_cursor = self.pull_request_file_cursor;
        self.rebuild_pull_request_all_files_document();
    }

    pub(super) fn rebuild_pull_request_all_files_document(&mut self) {
        let source = self
            .pull_request_diff_source
            .unwrap_or(PullRequestDiffSource::PullRequest);
        let Some(mut pull_request) = self.pull_request_for_diff_source(source) else {
            return;
        };
        let title = match source {
            PullRequestDiffSource::PullRequest => format!(
                "PR #{} : All Files · {} changed",
                pull_request.number, self.pull_request_total_files
            ),
            PullRequestDiffSource::Stack { from, to } => format!(
                "Stack · {from}..{to} · {} changed",
                self.pull_request_total_files
            ),
        };
        if matches!(source, PullRequestDiffSource::Stack { .. }) {
            pull_request.changed_files = self.pull_request_total_files;
            pull_request.additions = self
                .pull_request_files
                .iter()
                .filter_map(|file| file.counts)
                .map(|counts| counts.additions)
                .sum();
            pull_request.deletions = self
                .pull_request_files
                .iter()
                .filter_map(|file| file.counts)
                .map(|counts| counts.deletions)
                .sum();
        }
        if self.pull_request_files.is_empty() {
            let mut document = DiffDocument::empty(
                title,
                if self.pull_request_files_truncated {
                    "The changed-file index was truncated before any paths were read"
                } else {
                    "This pull request has no changed files"
                },
            );
            document.pull_request_details = Some(pull_request_details(&pull_request));
            self.set_document(document);
            return;
        }
        let index = DiffIndex {
            title,
            files: self
                .pull_request_files
                .iter()
                .map(|file| crate::git::diff::DiffFileIndexEntry {
                    path: file.path.clone(),
                    old_path: file.old_path.clone(),
                    status: pull_request_file_status_label(file.status).to_owned(),
                    counts: file.counts,
                })
                .collect(),
            truncated: self.pull_request_files_truncated,
            commit_details: None,
        };
        let paths = index
            .files
            .iter()
            .map(|file| file.path.clone())
            .collect::<Vec<_>>();
        let visible = self.visible_preview_paths(&paths);
        let mut document = index
            .document_with_visibility(&self.pull_request_documents, |path| visible.contains(path));
        document.pull_request_details = Some(pull_request_details(&pull_request));
        self.pull_request_restore_scroll = self.pull_request_scroll_anchor.is_some();
        self.set_document(document);
    }

    pub(super) fn cache_current_pull_request_single_document(&mut self) {
        let Some(path) = self.pull_request_single_file.take() else {
            return;
        };
        if self.pull_request_documents.contains_key(&path) {
            return;
        }
        let mut document = std::mem::take(&mut self.document);
        document
            .lines
            .retain(|line| line.kind != DiffLineKind::Review);
        self.invalidate_diff_rows();
        self.cache_pull_request_document(path, document);
    }

    pub(super) fn cache_pull_request_document(&mut self, path: PathBuf, document: DiffDocument) {
        if let Some(previous) = self.pull_request_documents.remove(&path) {
            self.pull_request_document_bytes = self
                .pull_request_document_bytes
                .saturating_sub(diff_document_size(&previous));
            self.pull_request_document_order
                .retain(|candidate| candidate != &path);
        }
        self.pull_request_document_bytes = self
            .pull_request_document_bytes
            .saturating_add(diff_document_size(&document));
        self.pull_request_document_order.push_back(path.clone());
        drop(self.pull_request_documents.insert(path, document));
        self.prune_pull_request_documents(MAX_PULL_REQUEST_DOCUMENT_BYTES);
    }

    pub(super) fn prune_pull_request_documents(&mut self, maximum_bytes: usize) {
        while self.pull_request_document_bytes > maximum_bytes
            && self.pull_request_documents.len() > 1
        {
            let position = self
                .pull_request_document_order
                .iter()
                .position(|path| !self.pull_request_viewport_paths.contains(path))
                .unwrap_or_default();
            let Some(expired) = self.pull_request_document_order.remove(position) else {
                break;
            };
            if let Some(document) = self.pull_request_documents.remove(&expired) {
                self.pull_request_document_bytes = self
                    .pull_request_document_bytes
                    .saturating_sub(diff_document_size(&document));
            }
        }
    }

    pub(super) fn take_pull_request_document(&mut self, path: &Path) -> Option<DiffDocument> {
        let document = self.pull_request_documents.remove(path)?;
        self.pull_request_document_bytes = self
            .pull_request_document_bytes
            .saturating_sub(diff_document_size(&document));
        self.pull_request_document_order
            .retain(|candidate| candidate != path);
        Some(document)
    }

    pub(super) fn show_pull_request_all_files(&mut self) {
        self.cache_current_pull_request_single_document();
        self.pull_request_file_view = PullRequestFileView::AllFiles;
        self.pull_request_loading_path = None;
        self.document_loading = false;
        self.selected_preview_file = self
            .selected_pull_request_file()
            .map(|file| file.path.clone());
        self.preview_file_cursor = self.pull_request_file_cursor;
        self.rebuild_pull_request_all_files_document();
    }

    pub(super) fn select_pull_request_section(
        &mut self,
        section: PullRequestSection,
        effects: &mut Vec<AppEffect>,
    ) {
        if self.pull_request_stack.is_some() && section != PullRequestSection::Stack {
            return;
        }
        match section {
            PullRequestSection::Overview => {
                if self.pull_request_section == section {
                    return;
                }
                self.invalidate_preview();
                self.pull_request_section = section;
                self.reset_sidebar_scroll();
                self.content_scroll = 0;
                self.horizontal_scroll = 0;
                self.request_pull_request_checks(false, effects);
                self.request_pull_request_conversation(false, effects);
                self.request_pull_request_review(false, effects);
                self.request_check_run_log(false, effects);
            }
            PullRequestSection::Files => {
                if self.pull_request_section == section
                    && self.pull_request_file_view == PullRequestFileView::AllFiles
                {
                    return;
                }
                self.invalidate_preview();
                self.pull_request_section = section;
                self.reset_sidebar_scroll();
                self.content_scroll = 0;
                self.horizontal_scroll = 0;
                self.show_pull_request_all_files();
                self.request_pull_request_review(false, effects);
                self.request_preview(effects);
            }
            PullRequestSection::Stack => {
                if self.pull_request_stack.is_none() || self.pull_request_section == section {
                    return;
                }
                self.invalidate_preview();
                self.pull_request_section = section;
                self.pull_request_file_view = PullRequestFileView::AllFiles;
                self.reset_sidebar_scroll();
                self.content_scroll = 0;
                self.horizontal_scroll = 0;
                self.reconcile_stack_inspector();
                self.request_stack_inspector(false, effects);
            }
        }
    }

    pub(super) fn request_pull_request_diff_file(
        &mut self,
        path: PathBuf,
        show_loading: bool,
        effects: &mut Vec<AppEffect>,
    ) {
        if !self.pull_request_files.iter().any(|file| file.path == path) {
            return;
        }
        if self.pull_request_file_view == PullRequestFileView::SingleFile {
            if self.pull_request_documents.contains_key(&path) {
                self.cache_current_pull_request_single_document();
            }
            if let Some(document) = self.take_pull_request_document(&path) {
                self.document_loading = false;
                self.set_document(document);
                self.pull_request_single_file = Some(path);
                self.selected_preview_file = None;
                self.preview_file_cursor = 0;
                self.decorate_pull_request_review();
                return;
            }
        } else if self.pull_request_documents.contains_key(&path) {
            self.document_loading = false;
            self.rebuild_pull_request_all_files_document();
            return;
        }
        let Some(workspace_generation) = self.pull_request_workspace_generation else {
            return;
        };
        self.diff_generation = self.diff_generation.wrapping_add(1);
        self.pull_request_loading_path = Some(path.clone());
        self.document_loading = show_loading;
        self.pull_request_progress = None;
        effects.push(AppEffect::Git(Box::new(
            WorkerCommand::LoadPullRequestFile {
                generation: self.diff_generation,
                workspace_generation,
                path,
            },
        )));
    }

    #[doc = " A path still needs its patch unless it is already cached, already in"]
    #[doc = " flight, or currently occupying the single-file document."]
    pub(super) fn pull_request_file_needs_patch(&self, path: &Path) -> bool {
        !self.pull_request_documents.contains_key(path)
            && self.pull_request_loading_path.as_deref() != Some(path)
            && self.pull_request_single_file.as_deref() != Some(path)
    }

    #[doc = " A finished patch knows its real totals, so a file whose counts GitHub"]
    #[doc = " could not report fills its header in as soon as its document arrives."]
    pub(super) fn backfill_pull_request_counts(
        &mut self,
        path: &Path,
        document: &DiffDocument,
    ) -> bool {
        if document.truncated {
            return false;
        }
        let Some(file) = self
            .pull_request_files
            .iter_mut()
            .find(|file| file.path == path && file.counts.is_none())
        else {
            return false;
        };
        let mut additions = 0_usize;
        let mut deletions = 0_usize;
        for line in &document.lines {
            match line.kind {
                DiffLineKind::Added => additions = additions.saturating_add(1),
                DiffLineKind::Removed => deletions = deletions.saturating_add(1),
                _ => {}
            }
        }
        file.counts = Some(DiffLineCounts {
            additions,
            deletions,
            binary: false,
        });
        true
    }
}