nexedit 0.2.2

A vim-like text editor, with simple shortcuts.
Documentation
use crate::errors::*;
use crate::models::application::modes::{SearchSelectConfig, SearchSelectMode};
use crate::util::SelectableVec;
use fragment;
use fragment::matching::AsStr;
use scribe::buffer::{Position, Token, TokenSet};
use std::clone::Clone;
use std::fmt;
use std::iter::Iterator;
use std::slice::Iter;
use std::str::FromStr;
use syntect::highlighting::ScopeSelectors;

pub struct SymbolJumpMode {
    insert: bool,
    input: String,
    symbols: Vec<Symbol>,
    results: SelectableVec<Symbol>,
    config: SearchSelectConfig,
}

#[derive(PartialEq, Debug)]
pub struct Symbol {
    pub token: String,
    pub position: Position,
}

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

impl Clone for Symbol {
    fn clone(&self) -> Symbol {
        Symbol {
            token: self.token.clone(),
            position: self.position,
        }
    }

    fn clone_from(&mut self, source: &Self) {
        self.token = source.token.clone();
        self.position = source.position;
    }
}

impl AsStr for Symbol {
    fn as_str(&self) -> &str {
        &self.token
    }
}

impl SymbolJumpMode {
    pub fn new(tokens: &TokenSet, config: SearchSelectConfig) -> Result<SymbolJumpMode> {
        let symbols = symbols(tokens.iter().chain_err(|| BUFFER_PARSE_FAILED)?);

        Ok(SymbolJumpMode {
            insert: true,
            input: String::new(),
            symbols,
            results: SelectableVec::new(Vec::new()),
            config,
        })
    }
}

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

impl SearchSelectMode<Symbol> for SymbolJumpMode {
    fn search(&mut self) {
        let results = fragment::matching::find(&self.input, &self.symbols, self.config.max_results);

        self.results = SelectableVec::new(results.into_iter().map(|r| r.clone()).collect());
    }

    fn query(&mut self) -> &mut String {
        &mut self.input
    }

    fn insert_mode(&self) -> bool {
        self.insert
    }

    fn set_insert_mode(&mut self, insert_mode: bool) {
        self.insert = insert_mode;
    }

    fn results(&self) -> Iter<Symbol> {
        self.results.iter()
    }

    fn selection(&self) -> Option<&Symbol> {
        self.results.selection()
    }

    fn selected_index(&self) -> usize {
        self.results.selected_index()
    }

    fn select_previous(&mut self) {
        self.results.select_previous();
    }

    fn select_next(&mut self) {
        self.results.select_next();
    }

    fn config(&self) -> &SearchSelectConfig {
        &self.config
    }
}

fn symbols<'a, T>(tokens: T) -> Vec<Symbol>
where
    T: Iterator<Item = Token<'a>>,
{
    let eligible_scopes =
        ScopeSelectors::from_str("entity.name.function, entity.name.class, entity.name.struct")
            .unwrap();
    tokens
        .filter_map(|token| {
            if let Token::Lexeme(lexeme) = token {
                if eligible_scopes
                    .does_match(lexeme.scope.as_slice())
                    .is_some()
                {
                    return Some(Symbol {
                        token: lexeme.value.to_string(),
                        position: lexeme.position,
                    });
                }
            }

            None
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::{symbols, Symbol};
    use scribe::buffer::{Lexeme, Position, ScopeStack, Token};
    use std::str::FromStr;

    #[test]
    fn symbols_are_limited_to_functions() {
        let tokens = vec![
            Token::Lexeme(Lexeme {
                value: "text",
                position: Position { line: 0, offset: 0 },
                scope: ScopeStack::from_str("meta.block.rust").unwrap(),
            }),
            Token::Lexeme(Lexeme {
                value: "function",
                position: Position { line: 1, offset: 0 },
                scope: ScopeStack::from_str("entity.name.function").unwrap(),
            }),
            Token::Lexeme(Lexeme {
                value: "non-function",
                position: Position { line: 2, offset: 0 },
                scope: ScopeStack::from_str("meta.entity.name.function").unwrap(),
            }),
        ];

        let results = symbols(tokens.into_iter());
        assert_eq!(results.len(), 1);
        assert_eq!(
            results.first().unwrap(),
            &Symbol {
                token: "function".to_string(),
                position: Position { line: 1, offset: 0 }
            }
        );
    }
}