pub mod jump;
use frappe::Signal;
use ropey::Rope;
use Ropey;
use {Buffer, buffer};
pub struct Cursor {
positions: Vec<PositionSignal>
}
type PositionSignal = (Signal<usize>, Option<Signal<usize>>);
pub type Position = (usize, Option<usize>);
impl Cursor {
pub fn new<'a, I>(buf: &Buffer, positions: I) -> Self
where I: IntoIterator<Item=&'a Position> {
let mut cur = Self {
positions: Vec::new()
};
cur.set_positions(buf, positions);
cur
}
pub fn positions(&self) -> Positions {
Positions::new(self.positions.as_slice())
}
pub fn handle_cur(&mut self, buf: &Buffer, event: CursorEvent) {
match event {
CursorEvent::Jump(mut positions) => {
positions.sort();
positions.dedup();
self.set_positions(buf, positions.iter());
}
}
}
pub fn handle_buf(&mut self, buf: &mut Buffer, event: BufferEvent) {
let pos_lines = |text: &Rope, pos: Position| -> ::std::ops::Range<usize> {
let (char_idx, sel_start) = pos;
if let Some(sel_start) = sel_start {
let sel = self::select((sel_start, char_idx));
text.char_to_line(sel.start)..
(text.char_to_line(sel.end) + 1)
.min(text.len_lines())
} else {
let line = text.char_to_line(char_idx)
.min(text.len_lines().saturating_sub(1));
line..line + 1
}
};
match event {
BufferEvent::Insert(ref text) => {
for &mut (ref char_idx_signal, ref mut sel_start) in &mut self.positions {
if sel_start.is_some() { let start_idx = sel_start.as_ref().unwrap().sample();
let end_idx = char_idx_signal.sample();
*sel_start = None;
buf.handle(buffer::Event::Remove {
char_idx_range: select((start_idx, end_idx))
});
}
buf.handle(buffer::Event::Insert {
char_idx: char_idx_signal.sample(),
text: text.clone() });
}
}
BufferEvent::Backspace => {
for &mut (ref char_idx_signal, ref mut sel_start) in &mut self.positions {
let char_idx = char_idx_signal.sample();
if sel_start.is_some() {
let start_idx = sel_start.as_ref().unwrap().sample();
*sel_start = None;
buf.handle(buffer::Event::Remove {
char_idx_range: select((start_idx, char_idx))
});
} else if let (char_idx, false) = char_idx.overflowing_sub(1) {
buf.handle(buffer::Event::Remove {
char_idx_range: char_idx..char_idx + 1
});
}
}
}
BufferEvent::Delete => {
for &mut (ref char_idx_signal, ref mut sel_start) in &mut self.positions {
let char_idx = char_idx_signal.sample();
if sel_start.is_some() {
let start_idx = sel_start.as_ref().unwrap().sample();
*sel_start = None;
buf.handle(buffer::Event::Remove {
char_idx_range: select((start_idx, char_idx))
});
} else if char_idx < buf.text().len_chars() {
buf.handle(buffer::Event::Remove {
char_idx_range: char_idx..char_idx + 1
});
}
}
}
BufferEvent::Indent(indent) => indent.sample_with(|indent| {
if buf.text().len_chars() == 0 {
buf.handle(buffer::Event::Insert {
char_idx: 0,
text: (*indent).clone() })
} else {
for pos in self.positions() {
for line_idx in pos_lines(buf.text(), pos) {
let line_start_idx = buf.text().line_to_char(line_idx);
buf.handle(buffer::Event::Insert {
char_idx: line_start_idx,
text: (*indent).clone() })
}
}
}
}),
BufferEvent::Dedent(indent) => indent.sample_with(|indent| {
if buf.text().len_chars() == 0 { return }
for pos in self.positions() {
for line_idx in pos_lines(buf.text(), pos) {
if {
let line = buf.text().line(line_idx);
line.starts_with(&indent)
} {
let line_start_idx = buf.text().line_to_char(line_idx);
buf.handle(buffer::Event::Remove {
char_idx_range:
line_start_idx..
line_start_idx + indent.chars().count()
});
}
}
}
})
}
}
fn set_positions<'a, I>(&mut self, buf: &Buffer, positions: I)
where I: IntoIterator<Item=&'a Position> {
let positions = positions.into_iter()
.map(|&(char_idx, sel_start)| (
buf.signal_char_idx(char_idx),
sel_start.map(|sel_start| buf.signal_char_idx(sel_start))
));
self.positions.clear();
self.positions.extend(positions);
}
}
pub fn select(range: (usize, usize)) -> ::std::ops::Range<usize> {
let (start_idx, end_idx) = range;
if start_idx < end_idx {
start_idx..end_idx
} else {
end_idx..start_idx
}
}
pub enum Event {
Cur(CursorEvent),
Buf(BufferEvent)
}
pub enum CursorEvent {
Jump(Vec<Position>)
}
pub enum BufferEvent {
Insert(String),
Backspace,
Delete,
Indent(Signal<String>), Dedent(Signal<String>)
}
pub struct Positions<'a> {
items: &'a [PositionSignal],
idx: usize,
idx_back: usize
}
impl<'a> Positions<'a> {
pub fn new(positions: &'a [PositionSignal]) -> Self {
Positions {
items: positions,
idx: 0,
idx_back: 0
}
}
}
impl<'a> Iterator for Positions<'a> {
type Item = Position;
fn next(&mut self) -> Option<Self::Item> {
if self.idx < self.items.len() - self.idx_back {
let item = &self.items[self.idx];
self.idx += 1;
Some((
item.0.sample(),
match item.1 {
Some(ref signal) => Some(signal.sample()),
None => None
}
))
} else {
None
}
}
}
impl<'a> DoubleEndedIterator for Positions<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
let len = self.items.len();
if self.idx_back < len - self.idx {
let item = &self.items[len - 1 - self.idx_back];
self.idx_back += 1;
Some((
item.0.sample(),
match item.1 {
Some(ref signal) => Some(signal.sample()),
None => None
}
))
} else {
None
}
}
}
impl<'a> ExactSizeIterator for Positions<'a> {
fn len(&self) -> usize {
self.items.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEXT: &str = concat!(
"This is line number 1\n",
"Soon follows line number 2\n",
"Which, in turn, is followed by line number 3\n",
"In close call to line number 4\n",
"Within proximity of line number 5\n",
"Right beside line number 6\n"
);
#[test]
fn new() {
let mut buf = Buffer::from_str(TEXT);
let positions: Vec<Position> = TEXT.lines().enumerate()
.map(|(line_idx, _)| (buf.text().line_to_char(line_idx), None))
.collect();
let cur = Cursor::new(&mut buf, positions.iter());
assert_eq!(positions, cur.positions().collect::<Vec<_>>());
}
#[test]
fn insert() {
let mut buf = Buffer::from_str(TEXT);
let mut cur = Cursor::new(&mut buf, &[(0, None)]);
const CHARS: [char; 17] = [
'L', 'i', 'n', 'e', 's', ' ',
'b', 'e', 'g', 'i', 'n', ' ',
'a', 't', ' ', '0', '\n'
];
for char in &CHARS {
cur.handle_buf(&mut buf, BufferEvent::Insert(char.to_string()));
}
assert_eq!(CHARS.iter().collect::<String>(), buf.text().line(0).to_string());
const LINE_IDC: [usize; 2] = [0, 1];
let positions = LINE_IDC.iter()
.map(|line_idx| (buf.text().line_to_char(*line_idx), None))
.collect::<Vec<_>>();
cur.set_positions(&mut buf, &positions);
const PREFIX: &str = "->";
cur.handle_buf(&mut buf, BufferEvent::Insert(PREFIX.to_owned()));
for line_idx in &LINE_IDC {
assert!(buf.text().line(*line_idx).to_string().starts_with(PREFIX));
}
}
#[test]
fn backspace() {
let mut buf = Buffer::from_str(TEXT);
let mut cur = Cursor::new(&mut buf, &[(0, None)]);
cur.handle_buf(&mut buf, BufferEvent::Backspace);
assert_eq!("This is line number 1\n", buf.text().line(0).to_string());
let positions = [(buf.text().len_chars(), None)];
cur.set_positions(&mut buf, &positions);
cur.handle_buf(&mut buf, BufferEvent::Backspace);
let line_idx = buf.text().len_lines() - 1;
assert_eq!("Right beside line number 6", buf.text().line(line_idx).to_string());
for _ in 0..9 {
cur.handle_buf(&mut buf, BufferEvent::Backspace);
}
assert_eq!("Right beside line", buf.text().line(line_idx).to_string());
const LINE_IDC: [usize; 2] = [2, 3];
let positions = LINE_IDC.iter()
.map(|line_idx| (buf.text().line_to_char(*line_idx + 1) - 1, None))
.collect::<Vec<_>>();
cur.set_positions(&mut buf, &positions);
cur.handle_buf(&mut buf, BufferEvent::Backspace);
cur.handle_buf(&mut buf, BufferEvent::Backspace);
for line_idx in &LINE_IDC {
assert!(buf.text().line(*line_idx).to_string().ends_with("line number\n"));
}
}
}