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)
}
}
#[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));
}
}