1use ropey::Rope;
2use frappe::{Sink, Stream, Signal};
3use std::ops::{Deref, Range};
4
5pub struct Buffer {
6 text: Rope,
7 event_sink: Sink<FiringEvent>
8}
9
10impl Buffer {
11 pub fn from_str(text: &str) -> Self {
12 Self {
13 text: Rope::from_str(text),
14 event_sink: Sink::new()
15 }
16 }
17
18 pub fn text(&self) -> &Rope {
19 &self.text
20 }
21
22 pub fn events(&self) -> Stream<FiringEvent> {
23 self.event_sink.stream()
24 }
25
26 pub fn handle(&mut self, event: Event) {
27 let text = self.text.clone();
28
29 match *&event {
30 Event::Insert { char_idx, ref text } => self.text.insert(char_idx, text),
31 Event::Remove { ref char_idx_range } => self.text.remove(char_idx_range.clone())
32 }
33
34 self.event_sink.send(FiringEvent {
35 event: event,
36 text: text
37 });
38 }
39
40 pub fn signal_char_idx(&self, char_idx: usize) -> Signal<usize> {
41 let current_sink = Sink::new();
42 let current_signal = current_sink.stream().hold(char_idx);
43
44 self.event_sink.stream()
45 .map(move |event| {
46 let value = event.update_char_idx(current_signal.sample());
47 current_sink.send(value);
48 value
49 })
50 .hold(char_idx)
51 }
52
53 pub fn signal_line_idx(&self, line_idx: usize) -> Signal<usize> {
54 let current_sink = Sink::new();
55 let current_signal = current_sink.stream().hold(line_idx);
56
57 self.event_sink.stream()
58 .map(move |event| {
59 let value = event.update_line_idx(current_signal.sample());
60 current_sink.send(value);
61 value
62 })
63 .hold(line_idx)
64 }
65}
66
67#[derive(Clone)]
69pub struct FiringEvent {
70 event: Event,
71 text: Rope
72}
73
74impl FiringEvent {
75 fn update_char_idx(&self, idx: usize) -> usize {
76 match &**self {
77 &Event::Insert { char_idx, ref text } if
78 char_idx <= idx
79 => idx + text.chars().count(),
80
81 &Event::Remove { ref char_idx_range } if
82 char_idx_range.start < idx
83 => if char_idx_range.end <= idx {
84 idx - (char_idx_range.end - char_idx_range.start)
85 } else {
86 char_idx_range.start
87 },
88
89 _ => idx
90 }
91 }
92
93 fn update_line_idx(&self, line_idx: usize) -> usize {
94 match &**self {
95 &Event::Insert { char_idx, ref text } if
96 char_idx <= self.text.line_to_char(line_idx)
97 => line_idx + text.matches('\n').count(),
98
99 &Event::Remove { ref char_idx_range } if
100 self.text.char_to_line(char_idx_range.start) < line_idx ||
101 self.text.char_to_line(char_idx_range.end) < line_idx
102 => line_idx - self.text.slice(char_idx_range.clone()).to_string().matches('\n').count(),
103
104 _ => line_idx
105 }
106 }
107}
108
109impl Deref for FiringEvent {
110 type Target = Event;
111
112 fn deref(&self) -> &Self::Target {
113 &self.event
114 }
115}
116
117#[derive(Clone)]
118pub enum Event {
119 Insert {
120 char_idx: usize,
121 text: String
122 },
123 Remove {
124 char_idx_range: Range<usize>
125 }
126}
127
128impl super::history::Undo for FiringEvent {
129 type Undo = FiringEvent;
130
131 fn undo(&self) -> Self::Undo {
132 FiringEvent {
133 event: match **self {
134 Event::Insert { char_idx, ref text } => Event::Remove {
135 char_idx_range: char_idx..char_idx + text.len()
136 },
137 Event::Remove { ref char_idx_range } => Event::Insert {
138 char_idx: char_idx_range.start,
139 text: self.text.slice(char_idx_range.clone()).to_string()
140 }
141 },
142 text: self.text.clone()
143 }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn from_str() {
153 let text = "Hello, Ropey!";
154 let buf = Buffer::from_str(text);
155 assert_eq!(text, buf.text().to_string());
156 }
157
158 #[test]
159 fn signal_char_idx() {
160 fn sample(char_idc: &(Signal<usize>, Signal<usize>, Signal<usize>)) -> (usize, usize, usize) {(
161 char_idc.0.sample(),
162 char_idc.1.sample(),
163 char_idc.2.sample()
164 )}
165
166 let mut buf = Buffer::from_str("Hello, Ropey!");
167 let char_idc = (
168 buf.signal_char_idx(5),
169 buf.signal_char_idx(7),
170 buf.signal_char_idx(12)
171 );
172 assert_eq!((5, 7, 12), sample(&char_idc));
173
174 buf.handle(Event::Insert {
175 char_idx: 5,
176 text: " there".to_owned()
177 });
178 assert_eq!("Hello there, Ropey!", buf.text().to_string());
179 assert_eq!((11, 13, 18), sample(&char_idc));
180
181 buf.handle(Event::Insert {
182 char_idx: 18,
183 text: " my friend".to_owned()
184 });
185 assert_eq!("Hello there, Ropey my friend!", buf.text().to_string());
186 assert_eq!((11, 13, 28), sample(&char_idc));
187
188 buf.handle(Event::Remove {
189 char_idx_range: 5..11
190 });
191 assert_eq!("Hello, Ropey my friend!", buf.text().to_string());
192 assert_eq!((5, 7, 22), sample(&char_idc));
193
194 buf.handle(Event::Remove {
195 char_idx_range: 12..22
196 });
197 assert_eq!("Hello, Ropey!", buf.text().to_string());
198 assert_eq!((5, 7, 12), sample(&char_idc));
199 }
200
201 #[test]
202 fn signal_line_idx() {
203 fn sample(line_idc: &(Signal<usize>, Signal<usize>, Signal<usize>)) -> (usize, usize, usize) {(
204 line_idc.0.sample(),
205 line_idc.1.sample(),
206 line_idc.2.sample()
207 )}
208
209 let mut buf = Buffer::from_str("1\n2\n3");
210 let line_idc = (
211 buf.signal_line_idx(0),
212 buf.signal_line_idx(1),
213 buf.signal_line_idx(2)
214 );
215 assert_eq!((0, 1, 2), sample(&line_idc));
216
217 buf.handle(Event::Insert {
218 char_idx: 5,
219 text: "\n4".to_owned()
220 });
221 assert_eq!("1\n2\n3\n4", buf.text().to_string());
222 assert_eq!((0, 1, 2), sample(&line_idc));
223
224 buf.handle(Event::Insert {
225 char_idx: 3,
226 text: "a".to_owned()
227 });
228 assert_eq!("1\n2a\n3\n4", buf.text().to_string());
229 assert_eq!((0, 1, 2), sample(&line_idc));
230
231 buf.handle(Event::Insert {
232 char_idx: 5,
233 text: "3!\n".to_owned()
234 });
235 assert_eq!("1\n2a\n3!\n3\n4", buf.text().to_string());
236 assert_eq!((0, 1, 3), sample(&line_idc));
237
238 buf.handle(Event::Remove {
239 char_idx_range: 9..11
240 });
241 assert_eq!("1\n2a\n3!\n3", buf.text().to_string());
242 assert_eq!((0, 1, 3), sample(&line_idc));
243
244 buf.handle(Event::Remove {
245 char_idx_range: 0..2
246 });
247 assert_eq!("2a\n3!\n3", buf.text().to_string());
248 assert_eq!((0, 0, 2), sample(&line_idc));
249
250 buf.handle(Event::Remove {
251 char_idx_range: 3..6
252 });
253 assert_eq!("2a\n3", buf.text().to_string());
254 assert_eq!((0, 0, 1), sample(&line_idc));
255
256 buf.handle(Event::Remove {
257 char_idx_range: 0..4
258 });
259 assert!(buf.text().to_string().is_empty());
260 assert_eq!((0, 0, 0), sample(&line_idc));
261 }
262}