quinjet 0.0.46

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

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ProjectOpenMode {
    Initial,
    CurrentTab,
    NewTab,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ProjectRow {
    Group(usize),
    Worktree {
        group_index: usize,
        tree_index: usize,
    },
}

impl App {
    pub(crate) fn first_project_worktree_index(
        groups: &[ProjectGroup],
        query: &str,
        collapsed: &HashSet<PathBuf>,
    ) -> usize {
        Self::filtered_project_rows(groups, query, collapsed)
            .iter()
            .position(|row| matches!(row, ProjectRow::Worktree { .. }))
            .unwrap_or_default()
    }

    pub(super) fn remember_collapsed_project_groups(&mut self, collapsed: &HashSet<PathBuf>) {
        self.collapsed_project_groups.clone_from(collapsed);
        #[cfg(not(test))]
        crate::state::record_collapsed_project_groups(collapsed);
    }

    pub(crate) fn all_project_groups_expanded(
        groups: &[ProjectGroup],
        collapsed: &HashSet<PathBuf>,
    ) -> bool {
        !groups.is_empty()
            && groups
                .iter()
                .all(|group| !collapsed.contains(&group.common_dir))
    }

    pub(crate) fn toggle_all_project_groups(
        groups: &[ProjectGroup],
        collapsed: &mut HashSet<PathBuf>,
    ) {
        if Self::all_project_groups_expanded(groups, collapsed) {
            collapsed.extend(groups.iter().map(|group| group.common_dir.clone()));
        } else {
            collapsed.clear();
        }
    }

    pub(crate) fn filtered_project_rows(
        groups: &[ProjectGroup],
        query: &str,
        collapsed: &HashSet<PathBuf>,
    ) -> Vec<ProjectRow> {
        let matching = Self::matching_project_rows(groups, query);
        let mut rows = Vec::new();
        for (group_index, group) in groups.iter().enumerate() {
            let trees = matching
                .iter()
                .filter_map(|(matching_group, tree_index)| {
                    (*matching_group == group_index).then_some(*tree_index)
                })
                .collect::<Vec<_>>();
            if trees.is_empty() {
                continue;
            }
            rows.push(ProjectRow::Group(group_index));
            if query.is_empty() && collapsed.contains(&group.common_dir) {
                continue;
            }
            rows.extend(trees.into_iter().map(|tree_index| ProjectRow::Worktree {
                group_index,
                tree_index,
            }));
        }
        rows
    }

    pub(crate) fn matching_project_rows(
        groups: &[ProjectGroup],
        query: &str,
    ) -> Vec<(usize, usize)> {
        let query = query.to_lowercase();
        let mut rows = Vec::new();
        for (group_index, group) in groups.iter().enumerate() {
            let group_matches = query.is_empty() || group.name.to_lowercase().contains(&query);
            for (tree_index, tree) in group.worktrees.iter().enumerate() {
                if tree.prunable.is_some() {
                    continue;
                }
                let tree_matches = tree.path.to_string_lossy().to_lowercase().contains(&query)
                    || tree.branch_label().to_lowercase().contains(&query);
                if group_matches || tree_matches {
                    rows.push((group_index, tree_index));
                }
            }
        }
        rows
    }
}