<commit_before>/// Model represents the global states needed in FZF.
/// It will also define how the states will be shown on the terminal


use std::sync::{Arc, RwLock};
use item::{Item, MatchedItem, MatchedRange};
use ncurses::*;
use std::cmp::{min, max};
use std::cell::RefCell;
use std::collections::HashSet;
use orderedvec::OrderedVec;
use curses::*;
use query::Query;
use util::eventbox::EventBox;
use event::Event;
use std::mem;
use std::time::{Instant, Duration};
use std::thread;

// The whole screen is:
//
//                  +---------------------------------------|
//                  | | |                                   | 5
//                  | | |               ^                   | 4
//   current cursor |>| |               |                   | 3
//                  | | |      lines    |                   | 2 cursor
//         selected | |>|--------------------------------   | 1
//                  | | |                                   | 0
//                  +---------------------------------------+
//          spinner |/| | (matched/total) (per%) [selected] |
//                  +---------------------------------------+
//                  | prompt>  query string                 |
//                  +---------------------------------------+
//

const SPINNER_DURATION: u32 = 200;
const SPINNERS: [char; 8] = ['-', '\\', '|', '/', '-', '\\', '|', '/'];


pub struct Model {
    eb: Arc<EventBox<Event>>,
    pub query: Query,

    num_matched: u64,
    num_total: u64,
    pub items: Arc<RwLock<Vec<Item>>>, // all items
    selected_indics: HashSet<usize>,
    pub matched_items: RefCell<OrderedVec<MatchedItem>>,

    processed_percentage: u64,
    pub multi_selection: bool,
    pub prompt: String,
    pub reading: bool,

    item_cursor: usize, // the index of matched item currently highlighted.
    line_cursor: usize, // line No.
    hscroll_offset: usize,

    max_y: i32,
    max_x: i32,
    width: usize,
    height: usize,

    pub tabstop: usize,
    curses: Curses,
    timer: Instant,
}

impl Model {
    pub fn new(eb: Arc<EventBox<Event>>, curses: Curses) -> Self {
        let (max_y, max_x) = curses.get_maxyx();
        let timer = Instant::now();

        Model {
            eb: eb,
            query: Query::new(),
            num_matched: 0,
            num_total: 0,
            items: Arc::new(RwLock::new(Vec::new())),
            selected_indics: HashSet::new(),
            matched_items: RefCell::new(OrderedVec::new()),
            processed_percentage: 100,
            multi_selection: false,
            prompt: "> ".to_string(),
            reading: false,
            item_cursor: 0,
            line_cursor: 0,
            hscroll_offset: 0,
            max_y: max_y,
            max_x: max_x,
            width: (max_x - 2) as usize,
            height: (max_y - 2) as usize,
            tabstop: 8,
            curses: curses,
            timer: timer,
        }
    }

    pub fn output(&self) {
        let mut selected = self.selected_indics.iter().collect::<Vec<&usize>>();
        selected.sort();
        let items = self.items.read().unwrap();
        for index in selected {
            println!("{}", items[*index].text);
        }
    }

    pub fn update_process_info(&mut self, matched: u64, total: u64, processed: u64) {
        self.num_matched = matched;
        self.num_total = total;
        self.processed_percentage = (processed+1)*100/(total+1);
    }

    pub fn push_item(&mut self, item: MatchedItem) {
        self.matched_items.borrow_mut().push(item);
    }

    pub fn clear_items(&mut self) {
        self.matched_items.borrow_mut().clear();
    }

    pub fn print_query(&self) {
        // > query
        mv(self.max_y-1, 0);
        self.curses.cprint(&self.prompt, COLOR_PROMPT, false);
        self.curses.cprint(&self.query.get_query(), COLOR_NORMAL, true);
        mv(self.max_y-1, (self.query.pos+self.prompt.len()) as i32);
    }

    pub fn print_info(&self) {
        mv(self.max_y-2, 0);

        if self.reading {
            let time = self.timer.elapsed();
            let mills = (time.as_secs()*1000) as u32 + time.subsec_nanos()/1000/1000;
            let index = (mills / SPINNER_DURATION) % (SPINNERS.len() as u32);
            self.print_char(SPINNERS[index as usize], COLOR_SPINNER, true);
        } else {
            self.print_char(' ', COLOR_NORMAL, false);
        }

        addstr(format!(" {}/{}{} ", self.num_matched, self.num_total,
                       if self.processed_percentage == 100 {"".to_string()} else {format!("({}%)", self.processed_percentage)},
                       ).as_str());
    }

    pub fn print_items(&self) {
        let mut matched_items = self.matched_items.borrow_mut();
        let item_start_pos = self.item_cursor - self.line_cursor;

        for i in 0..self.height {
            if let Some(matched) = matched_items.get(item_start_pos + i) {
                mv((self.height - i - 1) as i32, 0);

                let is_current_line = i == self.line_cursor;
                let label = if is_current_line {">"} else {" "};
                self.curses.cprint(label, COLOR_CURSOR, true);
                self.print_item(matched, is_current_line);
            } else {
                break;
            }
        }
    }

    fn print_item(&self, matched: &MatchedItem, is_current: bool) {
        let items = self.items.read().unwrap();
        let ref item = items[matched.index];

        let is_selected = self.selected_indics.contains(&matched.index);

        if is_selected {
            self.curses.cprint(">", COLOR_SELECTED, true);<commit_msg>[info] add selected items and matching processes.<commit_after>/// Model represents the global states needed in FZF.
/// It will also define how the states will be shown on the terminal


use std::sync::{Arc, RwLock};
use item::{Item, MatchedItem, MatchedRange};
use ncurses::*;
use std::cmp::{min, max};
use std::cell::RefCell;
use std::collections::HashSet;
use orderedvec::OrderedVec;
use curses::*;
use query::Query;
use util::eventbox::EventBox;
use event::Event;
use std::mem;
use std::time::{Instant};

// The whole screen is:
//
//                  +---------------------------------------|
//                  | | |                                   | 5
//                  | | |               ^                   | 4
//   current cursor |>| |               |                   | 3
//                  | | |      lines    |                   | 2 cursor
//         selected | |>|--------------------------------   | 1
//                  | | |                                   | 0
//                  +---------------------------------------+
//          spinner |/| | (matched/total) (per%) [selected] |
//                  +---------------------------------------+
//                  | prompt>  query string                 |
//                  +---------------------------------------+
//

const SPINNER_DURATION: u32 = 200;
const SPINNERS: [char; 8] = ['-', '\\', '|', '/', '-', '\\', '|', '/'];


pub struct Model {
    eb: Arc<EventBox<Event>>,
    pub query: Query,

    num_matched: u64,
    num_total: u64,
    pub items: Arc<RwLock<Vec<Item>>>, // all items
    selected_indics: HashSet<usize>,
    pub matched_items: RefCell<OrderedVec<MatchedItem>>,

    processed_percentage: u64,
    pub multi_selection: bool,
    pub prompt: String,
    pub reading: bool,

    item_cursor: usize, // the index of matched item currently highlighted.
    line_cursor: usize, // line No.
    hscroll_offset: usize,

    max_y: i32,
    max_x: i32,
    width: usize,
    height: usize,

    pub tabstop: usize,
    curses: Curses,
    timer: Instant,
}

impl Model {
    pub fn new(eb: Arc<EventBox<Event>>, curses: Curses) -> Self {
        let (max_y, max_x) = curses.get_maxyx();
        let timer = Instant::now();

        Model {
            eb: eb,
            query: Query::new(),
            num_matched: 0,
            num_total: 0,
            items: Arc::new(RwLock::new(Vec::new())),
            selected_indics: HashSet::new(),
            matched_items: RefCell::new(OrderedVec::new()),
            processed_percentage: 100,
            multi_selection: false,
            prompt: "> ".to_string(),
            reading: false,
            item_cursor: 0,
            line_cursor: 0,
            hscroll_offset: 0,
            max_y: max_y,
            max_x: max_x,
            width: (max_x - 2) as usize,
            height: (max_y - 2) as usize,
            tabstop: 8,
            curses: curses,
            timer: timer,
        }
    }

    pub fn output(&self) {
        let mut selected = self.selected_indics.iter().collect::<Vec<&usize>>();
        selected.sort();
        let items = self.items.read().unwrap();
        for index in selected {
            println!("{}", items[*index].text);
        }
    }

    pub fn update_process_info(&mut self, matched: u64, total: u64, processed: u64) {
        self.num_matched = matched;
        self.num_total = total;
        self.processed_percentage = (processed+1)*100/(total+1);
    }

    pub fn push_item(&mut self, item: MatchedItem) {
        self.matched_items.borrow_mut().push(item);
    }

    pub fn clear_items(&mut self) {
        self.matched_items.borrow_mut().clear();
    }

    pub fn print_query(&self) {
        // > query
        mv(self.max_y-1, 0);
        self.curses.cprint(&self.prompt, COLOR_PROMPT, false);
        self.curses.cprint(&self.query.get_query(), COLOR_NORMAL, true);
        mv(self.max_y-1, (self.query.pos+self.prompt.len()) as i32);
    }

    pub fn print_info(&self) {
        mv(self.max_y-2, 0);

        if self.reading {
            let time = self.timer.elapsed();
            let mills = (time.as_secs()*1000) as u32 + time.subsec_nanos()/1000/1000;
            let index = (mills / SPINNER_DURATION) % (SPINNERS.len() as u32);
            self.print_char(SPINNERS[index as usize], COLOR_SPINNER, true);
        } else {
            self.print_char(' ', COLOR_NORMAL, false);
        }

        self.curses.cprint(format!(" {}/{}", self.num_matched, self.num_total).as_ref(), COLOR_INFO, false);

        if self.multi_selection && self.selected_indics.len() > 0 {
            self.curses.cprint(format!(" [{}]", self.selected_indics.len()).as_ref(), COLOR_INFO, true);
        }

        if !self.reading && self.processed_percentage < 100 {
            self.curses.cprint(format!(" ({}%)", self.processed_percentage).as_ref(), COLOR_INFO, false);
        }
    }

    pub fn print_items(&self) {
        let mut matched_items = self.matched_items.borrow_mut();
        let item_start_pos = self.item_cursor - self.line_cursor;

        for i in 0..self.height {
            if let Some(matched) = matched_items.get(item_start_pos + i) {
                mv((self.height - i - 1) as i32, 0);

                let is_current_line = i == self.line_cursor;
                let label = if is_current_line {">"} else {" "};
                self.curses.cprint(label, COLOR_CURSOR, true);
                self.print_item(matched, is_current_line);
            } else {
                break;
            }
        }
    }

    fn print_item(&self, matched: &MatchedItem, is_current: bool) {
        let items = self.items.read().unwrap();
        let ref item = items[matched.index];

        let is_selected = self.selected_indics.contains(&matched.index);

        if is_selected {
            self.curses.cprint(">", COLOR_SELECTED, true);
{'fraction_non_alphanumeric': 0.16923344644566257, 'fraction_numerical': 0.013138432089097712, 'mean_word_length': 1.6532779316712836, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 44, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6558988', 'n_tokens_mistral': 3686, 'n_tokens_neox': 3482, 'n_words': 772}