ted 0.7.0

Core text editor functionality.
Documentation
use ropey::Rope;
use frappe::{Sink, Stream, Signal};
use std::ops::{Deref, Range};

pub struct Buffer {
    text: Rope,
    event_sink: Sink<FiringEvent>
}

impl Buffer {
    pub fn from_str(text: &str) -> Self {
        Self {
            text: Rope::from_str(text),
            event_sink: Sink::new()
        }
    }

    pub fn text(&self) -> &Rope {
        &self.text
    }

    pub fn events(&self) -> Stream<FiringEvent> {
        self.event_sink.stream()
    }

    pub fn handle(&mut self, event: Event) {
        let text = self.text.clone();

        match *&event {
            Event::Insert { char_idx, ref text } => self.text.insert(char_idx, text),
            Event::Remove { ref char_idx_range } => self.text.remove(char_idx_range.clone())
        }

        self.event_sink.send(FiringEvent {
            event: event,
            text: text
        });
    }

    pub fn signal_char_idx(&self, char_idx: usize) -> Signal<usize> {
        let current_sink = Sink::new();
        let current_signal = current_sink.stream().hold(char_idx);

        self.event_sink.stream()
            .map(move |event| {
                let value = event.update_char_idx(current_signal.sample());
                current_sink.send(value);
                value
            })
            .hold(char_idx)
    }

    pub fn signal_line_idx(&self, line_idx: usize) -> Signal<usize> {
        let current_sink = Sink::new();
        let current_signal = current_sink.stream().hold(line_idx);

        self.event_sink.stream()
            .map(move |event| {
                let value = event.update_line_idx(current_signal.sample());
                current_sink.send(value);
                value
            })
            .hold(line_idx)
    }
}

/// Event wrapper with text it applies to.
#[derive(Clone)]
pub struct FiringEvent {
    event: Event,
    text: Rope
}

impl FiringEvent {
    fn update_char_idx(&self, idx: usize) -> usize {
        match &**self {
            &Event::Insert { char_idx, ref text } if
                char_idx <= idx
             => idx + text.chars().count(),

            &Event::Remove { ref char_idx_range } if
                char_idx_range.start < idx
             => if char_idx_range.end <= idx {
                idx - (char_idx_range.end - char_idx_range.start)
             } else {
                char_idx_range.start
             },

            _ => idx
        }
    }

    fn update_line_idx(&self, line_idx: usize) -> usize {
        match &**self {
            &Event::Insert { char_idx, ref text } if
                char_idx <= self.text.line_to_char(line_idx)
             => line_idx + text.matches('\n').count(),

            &Event::Remove { ref char_idx_range } if
                self.text.char_to_line(char_idx_range.start) < line_idx ||
                self.text.char_to_line(char_idx_range.end)   < line_idx
             => line_idx - self.text.slice(char_idx_range.clone()).to_string().matches('\n').count(),

            _ => line_idx
        }
    }
}

impl Deref for FiringEvent {
    type Target = Event;

    fn deref(&self) -> &Self::Target {
        &self.event
    }
}

#[derive(Clone)]
pub enum Event {
    Insert {
        char_idx: usize,
        text: String
    },
    Remove {
        char_idx_range: Range<usize>
    }
}

impl super::history::Undo for FiringEvent {
    type Undo = FiringEvent;

    fn undo(&self) -> Self::Undo {
        FiringEvent {
            event: match **self {
                Event::Insert { char_idx, ref text } => Event::Remove {
                    char_idx_range: char_idx..char_idx + text.len()
                },
                Event::Remove { ref char_idx_range } => Event::Insert {
                    char_idx: char_idx_range.start,
                    text: self.text.slice(char_idx_range.clone()).to_string()
                }
            },
            text: self.text.clone()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn from_str() {
        let text = "Hello, Ropey!";
        let buf = Buffer::from_str(text);
        assert_eq!(text, buf.text().to_string());
    }

    #[test]
    fn signal_char_idx() {
        fn sample(char_idc: &(Signal<usize>, Signal<usize>, Signal<usize>)) -> (usize, usize, usize) {(
            char_idc.0.sample(),
            char_idc.1.sample(),
            char_idc.2.sample()
        )}

        let mut buf = Buffer::from_str("Hello, Ropey!");
        let char_idc = (
            buf.signal_char_idx(5),
            buf.signal_char_idx(7),
            buf.signal_char_idx(12)
        );
        assert_eq!((5, 7, 12), sample(&char_idc));

        buf.handle(Event::Insert {
            char_idx: 5,
            text: " there".to_owned()
        });
        assert_eq!("Hello there, Ropey!", buf.text().to_string());
        assert_eq!((11, 13, 18), sample(&char_idc));

        buf.handle(Event::Insert {
            char_idx: 18,
            text: " my friend".to_owned()
        });
        assert_eq!("Hello there, Ropey my friend!", buf.text().to_string());
        assert_eq!((11, 13, 28), sample(&char_idc));

        buf.handle(Event::Remove {
            char_idx_range: 5..11
        });
        assert_eq!("Hello, Ropey my friend!", buf.text().to_string());
        assert_eq!((5, 7, 22), sample(&char_idc));

        buf.handle(Event::Remove {
            char_idx_range: 12..22
        });
        assert_eq!("Hello, Ropey!", buf.text().to_string());
        assert_eq!((5, 7, 12), sample(&char_idc));
    }

    #[test]
    fn signal_line_idx() {
        fn sample(line_idc: &(Signal<usize>, Signal<usize>, Signal<usize>)) -> (usize, usize, usize) {(
            line_idc.0.sample(),
            line_idc.1.sample(),
            line_idc.2.sample()
        )}

        let mut buf = Buffer::from_str("1\n2\n3");
        let line_idc = (
            buf.signal_line_idx(0),
            buf.signal_line_idx(1),
            buf.signal_line_idx(2)
        );
        assert_eq!((0, 1, 2), sample(&line_idc));

        buf.handle(Event::Insert {
            char_idx: 5,
            text: "\n4".to_owned()
        });
        assert_eq!("1\n2\n3\n4", buf.text().to_string());
        assert_eq!((0, 1, 2), sample(&line_idc));

        buf.handle(Event::Insert {
            char_idx: 3,
            text: "a".to_owned()
        });
        assert_eq!("1\n2a\n3\n4", buf.text().to_string());
        assert_eq!((0, 1, 2), sample(&line_idc));

        buf.handle(Event::Insert {
            char_idx: 5,
            text: "3!\n".to_owned()
        });
        assert_eq!("1\n2a\n3!\n3\n4", buf.text().to_string());
        assert_eq!((0, 1, 3), sample(&line_idc));

        buf.handle(Event::Remove {
            char_idx_range: 9..11
        });
        assert_eq!("1\n2a\n3!\n3", buf.text().to_string());
        assert_eq!((0, 1, 3), sample(&line_idc));

        buf.handle(Event::Remove {
            char_idx_range: 0..2
        });
        assert_eq!("2a\n3!\n3", buf.text().to_string());
        assert_eq!((0, 0, 2), sample(&line_idc));

        buf.handle(Event::Remove {
            char_idx_range: 3..6
        });
        assert_eq!("2a\n3", buf.text().to_string());
        assert_eq!((0, 0, 1), sample(&line_idc));

        buf.handle(Event::Remove {
            char_idx_range: 0..4
        });
        assert!(buf.text().to_string().is_empty());
        assert_eq!((0, 0, 0), sample(&line_idc));
    }
}