nexedit 0.2.2

A vim-like text editor, with simple shortcuts.
Documentation
use super::application;
use crate::commands::{self, Result};
use crate::errors::*;
use crate::models::application::{Application, ClipboardContent, Mode};
use crate::util;
use crate::util::reflow::Reflow;
use scribe::buffer::{LineRange, Range};

pub fn delete(app: &mut Application) -> Result {
    let rng = sel_to_range(app)?;
    let buf = app.workspace.current_buffer.as_mut().unwrap();
    buf.delete_range(rng.clone());
    buf.cursor.move_to(rng.start());

    Ok(())
}

pub fn copy_and_delete(app: &mut Application) -> Result {
    let _ = copy_to_clipboard(app);
    delete(app)
}

pub fn change(app: &mut Application) -> Result {
    let _ = copy_to_clipboard(app);
    delete(app)?;
    application::switch_to_insert_mode(app)?;
    commands::view::scroll_to_cursor(app)
}

pub fn copy(app: &mut Application) -> Result {
    copy_to_clipboard(app)?;
    application::switch_to_normal_mode(app)
}

pub fn select_all(app: &mut Application) -> Result {
    app.workspace
        .current_buffer
        .as_mut()
        .ok_or(BUFFER_MISSING)?
        .cursor
        .move_to_first_line();
    application::switch_to_select_line_mode(app)?;
    app.workspace
        .current_buffer
        .as_mut()
        .ok_or(BUFFER_MISSING)?
        .cursor
        .move_to_last_line();

    Ok(())
}

pub fn justify(app: &mut Application) -> Result {
    let range = sel_to_range(app)?;
    let buffer = app.workspace.current_buffer.as_mut().unwrap();

    let limit = match app.preferences.borrow().line_length_guides()[..] {
        [first, ..] => first,
        [] => bail!("Justification requires a line_length_guide."),
    };

    buffer.start_operation_group();
    Reflow::new(buffer, range, limit)?.apply()?;
    buffer.end_operation_group();
    application::switch_to_normal_mode(app)
}

fn copy_to_clipboard(app: &mut Application) -> Result {
    let buffer = app
        .workspace
        .current_buffer
        .as_mut()
        .ok_or(BUFFER_MISSING)?;

    match app.mode {
        Mode::Select(ref select_mode) => {
            let cursor_position = *buffer.cursor.clone();
            let selected_range = Range::new(cursor_position, select_mode.anchor);

            let data = buffer
                .read(&selected_range)
                .ok_or("Couldn't read selected data from buffer")?;
            app.clipboard.set_content(ClipboardContent::Inline(data))?;
        }
        Mode::SelectLine(ref mode) => {
            let selected_range =
                util::inclusive_range(&LineRange::new(mode.anchor, buffer.cursor.line), buffer);

            let data = buffer
                .read(&selected_range)
                .ok_or("Couldn't read selected data from buffer")?;
            app.clipboard.set_content(ClipboardContent::Block(data))?;
        }
        _ => bail!("Can't copy data to clipboard outside of select modes"),
    };

    Ok(())
}

fn sel_to_range(app: &mut Application) -> std::result::Result<Range, Error> {
    let buf = app
        .workspace
        .current_buffer
        .as_mut()
        .ok_or(BUFFER_MISSING)?;

    match app.mode {
        Mode::Select(ref mode) => {
            let cursor_position = *buf.cursor.clone();
            Ok(Range::new(cursor_position, mode.anchor))
        }
        Mode::SelectLine(ref mode) => Ok(util::inclusive_range(
            &LineRange::new(mode.anchor, buf.cursor.line),
            buf,
        )),
        Mode::Search(ref mode) => Ok(mode
            .results
            .as_ref()
            .and_then(|r| r.selection())
            .ok_or("A selection is required.")?
            .clone()),
        _ => bail!("A selection is required."),
    }
}

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

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

        buffer.insert("nexedit");
        let position = Position { line: 1, offset: 3 };
        buffer.cursor.move_to(position);

        app.workspace.add_buffer(buffer);
        super::select_all(&mut app).unwrap();

        match app.mode {
            Mode::SelectLine(ref mode) => {
                assert_eq!(mode.anchor, 0);
            }
            _ => panic!("Application isn't in select line mode."),
        }

        assert_eq!(
            app.workspace.current_buffer.as_ref().unwrap().cursor.line,
            2
        );
    }

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

        buffer.insert("nexedit");
        let position = Position { line: 1, offset: 0 };
        buffer.cursor.move_to(position);

        app.workspace.add_buffer(buffer);
        commands::application::switch_to_select_mode(&mut app).unwrap();
        commands::cursor::move_right(&mut app).unwrap();
        commands::selection::delete(&mut app).unwrap();

        assert_eq!(
            app.workspace.current_buffer.as_ref().unwrap().data(),
            String::from("nexedit")
        )
    }

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

        buffer.insert("nexedit");
        let position = Position { line: 1, offset: 0 };
        buffer.cursor.move_to(position);

        app.workspace.add_buffer(buffer);
        commands::application::switch_to_select_line_mode(&mut app).unwrap();
        commands::selection::delete(&mut app).unwrap();

        assert_eq!(
            app.workspace.current_buffer.as_ref().unwrap().data(),
            String::from("nexedit")
        )
    }

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

        buffer.insert("nexedit");
        let position = Position { line: 1, offset: 0 };
        buffer.cursor.move_to(position);

        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();
        commands::selection::delete(&mut app).unwrap();

        assert_eq!(
            app.workspace.current_buffer.as_ref().unwrap().data(),
            String::from("nexedit")
        )
    }
}