argv-todo 0.1.0

A Vim-style terminal todo manager with nested tasks, priorities, search, and local SQLite storage
use crate::{
    app::{App, TaskPane},
    config::SortOrder,
    vim_motion::InputTarget,
};

pub(super) struct DisplayRow {
    pub(super) index: Option<usize>,
    pub(super) depth: usize,
    pub(super) ghost: bool,
}

pub(super) fn display_rows(app: &App, pane: TaskPane) -> (Vec<DisplayRow>, Option<usize>) {
    let mut visible: Vec<_> = app
        .rows_for_pane(pane)
        .into_iter()
        .map(|row| DisplayRow {
            index: Some(row.index),
            depth: row.depth,
            ghost: row.ghost,
        })
        .collect();
    let adding = pane == app.task_pane()
        && app.vim.input() == Some(InputTarget::Task)
        && app.editing_id.is_none();
    let draft = if adding {
        let (start, end, depth) = app
            .adding_parent
            .and_then(|id| {
                visible.iter().enumerate().find_map(|(row, task)| {
                    (task.index.is_some_and(|index| app.todos[index].id == id))
                        .then_some((row, task.depth))
                })
            })
            .map(|(parent, depth)| {
                let position = visible
                    .iter()
                    .enumerate()
                    .skip(parent + 1)
                    .find(|(_, task)| task.depth <= depth)
                    .map_or(visible.len(), |(row, _)| row);
                (parent + 1, position, depth + 1)
            })
            .unwrap_or((0, visible.len(), 0));
        let relative_position = app.adding_relative.and_then(|(id, above)| {
            app.todos
                .iter()
                .find(|todo| todo.id == id)
                .map(|todo| todo.position + i64::from(!above))
        });
        let position = (start..end)
            .find(|&row| {
                let Some(index) = visible[row].index else {
                    return false;
                };
                let todo = &app.todos[index];
                visible[row].depth == depth
                    && match app.sort_order() {
                        SortOrder::Priority => {
                            todo.priority > app.input_priority
                                || (todo.priority == app.input_priority
                                    && relative_position
                                        .is_some_and(|position| todo.position >= position))
                        }
                        SortOrder::Manual => {
                            relative_position.is_some_and(|position| todo.position >= position)
                        }
                    }
            })
            .unwrap_or(end);
        visible.insert(
            position,
            DisplayRow {
                index: None,
                depth,
                ghost: false,
            },
        );
        Some(position)
    } else {
        None
    };
    (visible, draft)
}