nexedit 0.2.2

A vim-like text editor, with simple shortcuts.
Documentation
use std::fmt::Display;
use std::slice::Iter;

#[derive(Clone)]
pub struct SearchSelectConfig {
    pub max_results: usize,
}

impl Default for SearchSelectConfig {
    fn default() -> SearchSelectConfig {
        SearchSelectConfig { max_results: 5 }
    }
}

pub trait SearchSelectMode<T: Display>: Display {
    fn query(&mut self) -> &mut String;
    fn search(&mut self);
    fn insert_mode(&self) -> bool;
    fn set_insert_mode(&mut self, insert_mode: bool);
    fn results(&self) -> Iter<T>;
    fn selection(&self) -> Option<&T>;
    fn selected_index(&self) -> usize;
    fn select_previous(&mut self);
    fn select_next(&mut self);
    fn config(&self) -> &SearchSelectConfig;
    fn message(&mut self) -> Option<String> {
        if self.query().is_empty() {
            Some(String::from("Enter a search query to start."))
        } else if self.results().count() == 0 {
            Some(String::from("No matching entries found."))
        } else {
            None
        }
    }

    fn push_search_char(&mut self, c: char) {
        self.query().push(c);
    }

    fn pop_search_token(&mut self) {
        let query = self.query();

        let mut boundary_index = 0;
        query
            .char_indices()
            .fold(true, |was_whitespace, (index, c)| {
                if c.is_whitespace() != was_whitespace {
                    boundary_index = index;
                }

                c.is_whitespace()
            });

        query.truncate(boundary_index);
    }
}

#[cfg(test)]
mod tests {
    use super::{SearchSelectConfig, SearchSelectMode};
    use std::fmt;
    use std::slice::Iter;

    #[derive(Default)]
    struct TestMode {
        input: String,
        selection: String,
        results: Vec<String>,
        config: SearchSelectConfig,
    }

    impl fmt::Display for TestMode {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "TEST")
        }
    }

    impl SearchSelectMode<String> for TestMode {
        fn query(&mut self) -> &mut String {
            &mut self.input
        }

        fn search(&mut self) {}
        fn insert_mode(&self) -> bool {
            false
        }
        fn set_insert_mode(&mut self, _: bool) {}
        fn results(&self) -> Iter<String> {
            self.results.iter()
        }
        fn selection(&self) -> Option<&String> {
            Some(&self.selection)
        }
        fn selected_index(&self) -> usize {
            0
        }
        fn select_previous(&mut self) {}
        fn select_next(&mut self) {}
        fn config(&self) -> &SearchSelectConfig {
            &self.config
        }
    }

    #[test]
    fn push_search_char_updates_query() {
        let mut mode = TestMode {
            ..Default::default()
        };
        mode.push_search_char('a');
        assert_eq!(mode.query(), "a");
    }

    #[test]
    fn pop_search_token_pops_all_characters_when_on_only_token() {
        let mut mode = TestMode {
            input: String::from("nexedit"),
            ..Default::default()
        };
        mode.pop_search_token();
        assert_eq!(mode.query(), "");
    }

    #[test]
    fn pop_search_token_pops_all_adjacent_non_whitespace_characters_when_on_non_whitespace_character(
    ) {
        let mut mode = TestMode {
            input: String::from("nexedit"),
            ..Default::default()
        };
        mode.pop_search_token();
        assert_eq!(mode.query(), "nexedit ");
    }

    #[test]
    fn pop_search_token_pops_all_whitespace_characters_when_on_whitespace_character() {
        let mut mode = TestMode {
            input: String::from("nexedit  "),
            ..Default::default()
        };
        mode.pop_search_token();
        assert_eq!(mode.query(), "nexedit");
    }
}