1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use crate::*;
use crossterm::cursor::MoveToPreviousLine;
use crossterm::terminal::Clear;
use crossterm::terminal::ClearType::FromCursorDown;
use crossterm::ExecutableCommand;
use requestty::question::Choice;
use requestty::Question;
use std::cmp::min;
use std::fmt::{Debug, Formatter};
use std::io::stdout;

const PAGE_MAX: usize = 25;
const NEXT_PAGE_LABEL: &str = "Next page";
const PREV_PAGE_LABEL: &str = "Previous page";
const GIVE_UP_LABEL: &str = "I can't see what I'm looking for";

pub fn get_search_term() -> Result<String> {
    let question = Question::input("search_term")
        .message("Please enter the name of the movie/show you're looking for")
        .build();
    Ok(requestty::prompt_one(question)?.try_into_string().unwrap())
}

pub struct Pager<'a> {
    choices: Vec<Choice<String>>,
    search_results: &'a Vec<SearchResult>,
    page_size: usize,
    page_index: usize,
    max_page_index: usize,
}

impl<'a> Pager<'a> {
    pub fn new(search_results: &'a Vec<SearchResult>, config: &RuntimeConfig) -> Self {
        // Subtract 3 to account for separator and misc. options
        let page_size = min(config.number_of_results, PAGE_MAX - 3);
        let choices = search_results
            .iter()
            .map(|sr| sr.to_string().into())
            .collect();
        Pager {
            choices,
            search_results,
            page_size,
            page_index: 0,
            max_page_index: search_results.len() / page_size,
        }
    }

    pub fn ask(&mut self) -> Result<&'a SearchResult> {
        let mut stdout = stdout();
        loop {
            let start_index = self.page_index * self.page_size;
            let end_index = min(start_index + self.page_size, self.choices.len());
            let mut displayed_choices = self.choices[start_index..end_index].to_vec();
            let results_being_shown = displayed_choices.len();
            displayed_choices.push(requestty::DefaultSeparator);

            if self.page_index < self.max_page_index {
                displayed_choices.push(Choice::Choice(NEXT_PAGE_LABEL.into()));
            }
            if self.page_index > 0 {
                displayed_choices.push(Choice::Choice(PREV_PAGE_LABEL.into()));
            }

            displayed_choices.push(Choice::Choice(GIVE_UP_LABEL.into()));

            let question = Question::select("")
                .message(if self.page_index == 0 {
                    String::from("Pick the correct search result")
                } else {
                    format!(
                        "Page {} of {}",
                        self.page_index + 1,
                        self.max_page_index + 1
                    )
                })
                .choices(displayed_choices)
                .build();

            let answer = requestty::prompt_one(question)?;
            let list_item = answer.as_list_item().unwrap();
            if list_item.index < results_being_shown {
                // Chose one of the search results
                return Ok(self
                    .search_results
                    .get(start_index + list_item.index)
                    .unwrap());
            } else {
                // Work out which other option has been chosen
                stdout
                    .execute(MoveToPreviousLine(1))?
                    .execute(Clear(FromCursorDown))?;
                match list_item.text.as_str() {
                    NEXT_PAGE_LABEL => self.next_page(),
                    PREV_PAGE_LABEL => self.prev_page(),
                    GIVE_UP_LABEL => return Err(RunError::NoDesiredSearchResults),
                    other => unreachable!("Please raise an issue because you managed to choose an option I didn't expect ({})", other),
                }
            }
        }
    }

    fn next_page(&mut self) {
        self.page_index = min(self.page_index + 1, self.max_page_index);
    }

    fn prev_page(&mut self) {
        self.page_index = self.page_index.saturating_sub(1);
    }
}

impl<'a> Debug for Pager<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "Search results: {}\n", self.search_results.len())?;
        write!(f, "Page size: {}\n", self.page_size)?;
        write!(f, "Number of pages: {}\n", self.max_page_index)?;
        write!(f, "Page index: {}\n", self.page_index)
    }
}