1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! 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)
        }
    }
}