nexedit 0.2.2

A vim-like text editor, with simple shortcuts.
Documentation
use crate::commands::{self, Result};
use crate::errors::*;
use crate::input::Key;
use crate::models::application::{Application, Mode};

pub fn move_to_previous_result(app: &mut Application) -> Result {
    if let Mode::Search(ref mut mode) = app.mode {
        mode.results
            .as_mut()
            .ok_or(NO_SEARCH_RESULTS)?
            .select_previous();
    } else {
        bail!("Can't move to search result outside of search mode");
    }

    commands::view::scroll_cursor_to_center(app).chain_err(|| SCROLL_TO_CURSOR_FAILED)?;
    move_to_current_result(app)
}

pub fn move_to_next_result(app: &mut Application) -> Result {
    if let Mode::Search(ref mut mode) = app.mode {
        mode.results
            .as_mut()
            .ok_or(NO_SEARCH_RESULTS)?
            .select_next();
    } else {
        bail!("Can't move to search result outside of search mode");
    }

    commands::view::scroll_cursor_to_center(app).chain_err(|| SCROLL_TO_CURSOR_FAILED)?;
    move_to_current_result(app)
}

pub fn move_to_current_result(app: &mut Application) -> Result {
    if let Mode::Search(ref mut mode) = app.mode {
        let buffer = app
            .workspace
            .current_buffer
            .as_mut()
            .ok_or(BUFFER_MISSING)?;
        let query = mode.input.as_ref().ok_or(SEARCH_QUERY_MISSING)?;
        let result = mode
            .results
            .as_mut()
            .ok_or(NO_SEARCH_RESULTS)?
            .selection()
            .ok_or_else(|| format!("No matches found for \"{}\"", query))?;
        buffer.cursor.move_to(result.start());
    } else {
        bail!("Can't move to search result outside of search mode");
    }

    commands::view::scroll_cursor_to_center(app).chain_err(|| SCROLL_TO_CURSOR_FAILED)?;

    Ok(())
}

pub fn accept_query(app: &mut Application) -> Result {
    if let Mode::Search(ref mut mode) = app.mode {
        mode.insert = false;
    } else {
        bail!("Can't accept search query outside of search mode");
    }
    run(app)?;

    Ok(())
}

pub fn clear_query(app: &mut Application) -> Result {
    if let Mode::Search(ref mut mode) = app.mode {
        mode.input = None;
        app.search_query = None;
    } else {
        bail!("Can't clear search outside of search mode");
    };

    Ok(())
}

pub fn push_search_char(app: &mut Application) -> Result {
    let key = app
        .view
        .last_key()
        .as_ref()
        .ok_or("View hasn't tracked a key press")?;

    if let Key::Char(c) = *key {
        if let Mode::Search(ref mut mode) = app.mode {
            let query = mode.input.get_or_insert(String::new());
            query.push(c);
            app.search_query = Some(query.clone());
        } else {
            bail!("Can't push search character outside of search mode");
        }
    } else {
        bail!("Last key press wasn't a character")
    }

    Ok(())
}

pub fn pop_search_char(app: &mut Application) -> Result {
    if let Mode::Search(ref mut mode) = app.mode {
        let query = mode.input.as_mut().ok_or(SEARCH_QUERY_MISSING)?;

        query.pop();
        app.search_query = Some(query.clone());
    } else {
        bail!("Can't pop search character outside of search mode");
    };

    Ok(())
}

pub fn run(app: &mut Application) -> Result {
    if let Mode::Search(ref mut mode) = app.mode {
        let buffer = app
            .workspace
            .current_buffer
            .as_ref()
            .ok_or(BUFFER_MISSING)?;
        mode.search(buffer)?;
    } else {
        bail!("Can't run search outside of search mode");
    }

    select_closest_result(app)?;
    move_to_current_result(app)
}

fn select_closest_result(app: &mut Application) -> Result {
    if let Mode::Search(ref mut mode) = app.mode {
        let buffer = app
            .workspace
            .current_buffer
            .as_ref()
            .ok_or(BUFFER_MISSING)?;
        let results = mode.results.as_mut().ok_or(NO_SEARCH_RESULTS)?;

        let skip_count = results
            .iter()
            .filter(|r| r.start() <= *buffer.cursor)
            .count();
        for _ in 0..skip_count {
            results.select_next();
        }
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use crate::commands;
    use crate::models::application::Mode;
    use crate::models::Application;
    use scribe::buffer::Position;
    use scribe::Buffer;

    #[test]
    fn move_to_previous_result_moves_cursor_to_previous_result() {
        let mut app = Application::new(&Vec::new()).unwrap();
        let mut buffer = Buffer::new();
        buffer.insert("nexedit");

        buffer.cursor.move_to(Position { line: 1, offset: 3 });
        app.workspace.add_buffer(buffer);

        commands::application::switch_to_search_mode(&mut app).unwrap();
        if let Mode::Search(ref mut mode) = app.mode {
            mode.input = Some(String::from("ed"));
        }
        commands::search::accept_query(&mut app).unwrap();

        commands::search::move_to_previous_result(&mut app).unwrap();

        assert_eq!(
            *app.workspace.current_buffer.as_ref().unwrap().cursor,
            Position { line: 1, offset: 0 }
        );
    }

    #[test]
    fn move_to_previous_result_wraps_to_the_end_of_the_document() {
        let mut app = Application::new(&Vec::new()).unwrap();
        let mut buffer = Buffer::new();
        buffer.insert("nexedit");
        app.workspace.add_buffer(buffer);

        commands::application::switch_to_search_mode(&mut app).unwrap();
        if let Mode::Search(ref mut mode) = app.mode {
            mode.input = Some(String::from("ed"));
        }
        commands::search::accept_query(&mut app).unwrap();

        commands::search::move_to_previous_result(&mut app).unwrap();

        assert_eq!(
            *app.workspace.current_buffer.as_ref().unwrap().cursor,
            Position { line: 2, offset: 0 }
        );
    }

    #[test]
    fn move_to_next_result_moves_cursor_to_next_result() {
        let mut app = Application::new(&Vec::new()).unwrap();
        let mut buffer = Buffer::new();
        buffer.insert("nexedit");
        app.workspace.add_buffer(buffer);

        commands::application::switch_to_search_mode(&mut app).unwrap();
        if let Mode::Search(ref mut mode) = app.mode {
            mode.input = Some(String::from("ed"));
        }
        commands::search::accept_query(&mut app).unwrap();

        commands::search::move_to_next_result(&mut app).unwrap();

        assert_eq!(
            *app.workspace.current_buffer.as_ref().unwrap().cursor,
            Position { line: 1, offset: 0 }
        );
    }

    #[test]
    fn move_to_next_result_wraps_to_the_start_of_the_document() {
        let mut app = Application::new(&Vec::new()).unwrap();
        let mut buffer = Buffer::new();
        buffer.insert("nexedit");

        buffer.cursor.move_to(Position { line: 1, offset: 3 });
        app.workspace.add_buffer(buffer);

        commands::application::switch_to_search_mode(&mut app).unwrap();
        if let Mode::Search(ref mut mode) = app.mode {
            mode.input = Some(String::from("ed"));
        }
        commands::search::accept_query(&mut app).unwrap();

        commands::search::move_to_next_result(&mut app).unwrap();

        assert_eq!(
            *app.workspace.current_buffer.as_ref().unwrap().cursor,
            Position { line: 0, offset: 4 }
        );
    }

    #[test]
    fn accept_query_disables_insert_sub_mode_and_moves_to_next_match() {
        let mut app = Application::new(&Vec::new()).unwrap();
        let mut buffer = Buffer::new();
        buffer.insert("nexedit");

        buffer.cursor.move_to(Position { line: 0, offset: 4 });
        app.workspace.add_buffer(buffer);

        app.search_query = Some(String::from("ed"));
        commands::application::switch_to_search_mode(&mut app).unwrap();
        commands::search::accept_query(&mut app).unwrap();

        assert!(match app.mode {
            crate::models::application::Mode::Search(ref mode) => !mode.insert_mode(),
            _ => false,
        });

        assert_eq!(app.search_query, Some("ed".to_string()));

        assert_eq!(
            *app.workspace.current_buffer.as_ref().unwrap().cursor,
            Position { line: 1, offset: 0 }
        );
    }
}