ted 0.7.0

Core text editor functionality.
Documentation
//! A command, when run, produces either:
//! - a no-op (e.g. `None`)
//! - an event
//! - multiple events (ordered or not)

use frappe::Signal;

/// These produce a `cursor::Event`.
///
/// Movement commands carry a boolean
/// that indicates whether to select.
#[derive(Clone)]
pub enum CursorCmd {
    Enter,
    Backspace, Delete,
    Indent(Signal<String>), Dedent(Signal<String>),

    // TODO name these better
    Left(bool), Right(bool),
    /// `(select, num_lines)`
    Vertical(bool, Signal<isize>),
    /// `(select, smart)`
    LineHome(bool, bool),
    LineEnd(bool),
    WordLeft(bool), WordRight(bool),
    SelectWord,
    SpawnMultiCursor, KillMultiCursor, SkipMultiCursor
}

use {Buffer, Cursor, cursor};

impl CursorCmd {
    pub fn run(&self, cur: &Cursor, buf: &Buffer) -> Option<cursor::Event> {
        use cursor::{Event, CursorEvent, BufferEvent};

        match self {
            // Event::Buf
            &CursorCmd::Enter              => Some(Event::Buf(BufferEvent::Insert('\n'.to_string()))),
            &CursorCmd::Backspace          => Some(Event::Buf(BufferEvent::Backspace)),
            &CursorCmd::Delete             => Some(Event::Buf(BufferEvent::Delete)),
            &CursorCmd::Indent(ref indent) => Some(Event::Buf(BufferEvent::Indent(indent.clone()))),
            &CursorCmd::Dedent(ref indent) => Some(Event::Buf(BufferEvent::Dedent(indent.clone()))),

            // Event::Cur
            &CursorCmd::Left(select) => Some(Event::Cur(CursorEvent::Jump(
                cur.positions()
                    .map(|(char_idx, sel_start)| (
                        match sel_start {
                            Some(sel_start) if !select => cursor::select((sel_start, char_idx)).start,
                            _                          => cursor::jump::left(char_idx)
                        },
                        if select { sel_start.or(Some(char_idx)) } else { None }
                    ))
                    .collect()
            ))),
            &CursorCmd::Right(select) => Some(Event::Cur(CursorEvent::Jump(
                cur.positions()
                    .map(|(char_idx, sel_start)| (
                        match sel_start {
                            Some(sel_start) if !select => cursor::select((sel_start, char_idx)).end,
                            _                          => cursor::jump::right(buf.text(), char_idx)
                        },
                        if select { sel_start.or(Some(char_idx)) } else { None }
                    ))
                    .collect()
            ))),
            &CursorCmd::Vertical(select, ref num_lines) => Some(Event::Cur(CursorEvent::Jump(
                cur.positions()
                    .map(|(char_idx, sel_start)| (
                        cursor::jump::vertical(buf.text(), char_idx, num_lines.sample()),
                        if select { sel_start.or(Some(char_idx)) } else { None }
                    ))
                    .collect()
            ))),
            // TODO smart home
            &CursorCmd::LineHome(select, _smart) => Some(Event::Cur(CursorEvent::Jump(
                cur.positions()
                    .map(|(char_idx, sel_start)| (
                        cursor::jump::line_start(buf.text(), char_idx),
                        if select { sel_start.or(Some(char_idx)) } else { None }
                    ))
                    .collect()
            ))),
            &CursorCmd::LineEnd(select) => Some(Event::Cur(CursorEvent::Jump(
                cur.positions()
                    .map(|(char_idx, sel_start)| (
                        cursor::jump::line_end(buf.text(), char_idx),
                        if select { sel_start.or(Some(char_idx)) } else { None }
                    ))
                    .collect()
            ))),
            &CursorCmd::WordLeft(select) => Some(Event::Cur(CursorEvent::Jump(
                cur.positions()
                    .map(|(char_idx, sel_start)| (
                        cursor::jump::word_left(buf.text(), char_idx),
                        if select { sel_start.or(Some(char_idx)) } else { None }
                    ))
                    .collect()
            ))),
            &CursorCmd::WordRight(select) => Some(Event::Cur(CursorEvent::Jump(
                cur.positions()
                    .map(|(char_idx, sel_start)| (
                        cursor::jump::word_right(buf.text(), char_idx),
                        if select { sel_start.or(Some(char_idx)) } else { None }
                    ))
                    .collect()
            ))),
            &CursorCmd::SelectWord => Some(Event::Cur(CursorEvent::Jump(cursor::jump::select_words(cur, buf.text())))),
            &CursorCmd::SpawnMultiCursor => cursor::jump::spawn(cur, buf.text()).map(CursorEvent::Jump).map(Event::Cur),
            &CursorCmd::KillMultiCursor  => cursor::jump::die(cur)              .map(CursorEvent::Jump).map(Event::Cur),
            &CursorCmd::SkipMultiCursor  => cursor::jump::skip(cur, buf.text()) .map(CursorEvent::Jump).map(Event::Cur)
        }
    }
}