1pub mod graphemes;
2pub mod movement;
3pub mod tree;
4
5mod diff;
6
7use ropey::{Rope, RopeSlice};
8use std::{cmp, ops::Range};
9
10pub use self::{
11 diff::{DeleteOperation, OpaqueDiff},
12 graphemes::{ByteIndex, CharIndex, LineIndex, RopeExt, RopeGraphemes},
13 movement::Direction,
14};
15
16trait RopeCursorExt {
17 fn cursor_to_line(&self, cursor: &Cursor) -> usize;
18
19 #[allow(dead_code)]
20 fn slice_cursor(&self, cursor: &Cursor) -> RopeSlice;
21}
22
23impl RopeCursorExt for Rope {
24 fn cursor_to_line(&self, cursor: &Cursor) -> usize {
25 self.char_to_line(cursor.range.start)
26 }
27
28 fn slice_cursor(&self, cursor: &Cursor) -> RopeSlice {
29 self.slice(cursor.range.start..cursor.range.end)
30 }
31}
32
33#[derive(Clone, Debug, PartialEq)]
38pub struct Cursor {
39 range: Range<CharIndex>,
46 selection: Option<CharIndex>,
50 visual_horizontal_offset: Option<usize>,
51}
52
53impl Default for Cursor {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl Cursor {
60 pub fn new() -> Self {
61 Self {
62 range: 0..0,
63 selection: None,
64 visual_horizontal_offset: None,
65 }
66 }
67
68 pub fn with_range(range: Range<CharIndex>) -> Self {
69 Self {
70 range,
71 ..Self::new()
72 }
73 }
74
75 #[cfg(test)]
76 pub fn end_of_buffer(text: &Rope) -> Self {
77 Self {
78 range: text.prev_grapheme_boundary(text.len_chars())..text.len_chars(),
79 visual_horizontal_offset: None,
80 selection: None,
81 }
82 }
83
84 pub fn is_empty(&self) -> bool {
85 self.range.is_empty()
86 }
87
88 pub fn range(&self) -> Range<CharIndex> {
89 self.range.clone()
90 }
91
92 pub fn selection(&self) -> Range<CharIndex> {
93 match self.selection {
94 Some(selection) if selection > self.range.start => self.range.start..selection,
95 Some(selection) if selection < self.range.start => selection..self.range.start,
96 _ => self.range.clone(),
97 }
98 }
99
100 pub fn column_offset(&self, tab_width: usize, text: &Rope) -> usize {
101 let char_line_start = text.line_to_char(text.cursor_to_line(self));
102 graphemes::width(tab_width, &text.slice(char_line_start..self.range.start))
103 }
104
105 pub fn reconcile(&mut self, new_text: &Rope, diff: &OpaqueDiff) {
106 let OpaqueDiff {
107 char_index,
108 old_char_length,
109 new_char_length,
110 ..
111 } = *diff;
112
113 let modified_range = char_index..cmp::max(old_char_length, new_char_length);
114
115 if modified_range.start >= self.range.end {
117 return;
118 }
119
120 if modified_range.end <= self.range.start {
122 let (start, end) = (self.range.start, self.range.end);
123 if old_char_length > new_char_length {
124 let length_change = old_char_length - new_char_length;
125 self.range = start.saturating_sub(length_change)..end.saturating_sub(length_change);
126 } else {
127 let length_change = new_char_length - old_char_length;
128 self.range = start + length_change..end + length_change;
129 };
130 }
131
132 let grapheme_start =
134 new_text.prev_grapheme_boundary(cmp::min(self.range.end, new_text.len_chars()));
135 let grapheme_end = new_text.next_grapheme_boundary(grapheme_start);
136 self.range = grapheme_start..grapheme_end
137 }
138
139 pub fn begin_selection(&mut self) {
140 self.selection = Some(self.range.start)
141 }
142
143 pub fn clear_selection(&mut self) {
144 self.selection = None;
145 }
146
147 pub fn select_all(&mut self, text: &Rope) {
148 movement::move_to_start_of_buffer(text, self);
149 self.selection = Some(text.len_chars());
150 }
151
152 pub fn insert_char(&mut self, text: &mut Rope, character: char) -> OpaqueDiff {
155 self.clear_selection();
156 text.insert_char(self.range.start, character);
157 OpaqueDiff::new(
158 text.char_to_byte(self.range.start),
159 0,
160 character.len_utf8(),
161 self.range.start,
162 0,
163 1,
164 )
165 }
166
167 pub fn insert_chars(
168 &mut self,
169 text: &mut Rope,
170 characters: impl IntoIterator<Item = char>,
171 ) -> OpaqueDiff {
172 self.clear_selection();
173 let mut num_bytes = 0;
174 let mut num_chars = 0;
175 characters
176 .into_iter()
177 .enumerate()
178 .for_each(|(offset, character)| {
179 text.insert_char(self.range.start + offset, character);
180 num_bytes += character.len_utf8();
181 num_chars += 1;
182 });
183 OpaqueDiff::new(
184 text.char_to_byte(self.range.start),
185 0,
186 num_bytes,
187 self.range.start,
188 0,
189 num_chars,
190 )
191 }
192
193 pub fn delete_forward(&mut self, text: &mut Rope) -> DeleteOperation {
194 if text.len_chars() == 0 || text.len_chars() == self.range.start {
195 return DeleteOperation::empty();
196 }
197
198 let byte_range = text.char_to_byte(self.range.start)..text.char_to_byte(self.range.end);
199 let diff = OpaqueDiff::new(
200 byte_range.start,
201 byte_range.end - byte_range.start,
202 0,
203 self.range.start,
204 self.range.end - self.range.start,
205 0,
206 );
207 text.remove(self.range.clone());
208
209 let grapheme_start = self.range.start;
210 let grapheme_end = text.next_grapheme_boundary(self.range.start);
211 let deleted = text.slice(grapheme_start..grapheme_end).into();
212
213 *self = Cursor::with_range(grapheme_start..grapheme_end);
214
215 DeleteOperation { diff, deleted }
216 }
217
218 pub fn delete_backward(&mut self, text: &mut Rope) -> DeleteOperation {
219 if self.range.start > 0 {
220 movement::move_horizontally(text, self, Direction::Backward, 1);
221 self.delete_forward(text)
222 } else {
223 DeleteOperation::empty()
224 }
225 }
226
227 pub fn delete_line(&mut self, text: &mut Rope) -> DeleteOperation {
228 if text.len_chars() == 0 {
229 return DeleteOperation::empty();
230 }
231
232 let line_index = text.char_to_line(self.range.start);
234 let delete_range_start = text.line_to_char(line_index);
235 let delete_range_end = text.line_to_char(line_index + 1);
236 let deleted = text.slice(delete_range_start..delete_range_end).into();
237 let diff = OpaqueDiff::new(
238 text.char_to_byte(delete_range_start),
239 text.char_to_byte(delete_range_end) - text.char_to_byte(delete_range_start),
240 0,
241 delete_range_start,
242 delete_range_end - delete_range_start,
243 0,
244 );
245 text.remove(delete_range_start..delete_range_end);
246
247 let grapheme_start =
249 text.line_to_char(cmp::min(line_index, text.len_lines().saturating_sub(2)));
250 let grapheme_end = text.next_grapheme_boundary(grapheme_start);
251
252 *self = Cursor::with_range(grapheme_start..grapheme_end);
253
254 DeleteOperation { diff, deleted }
255 }
256
257 pub fn delete_selection(&mut self, text: &mut Rope) -> DeleteOperation {
258 if text.len_chars() == 0 {
259 return DeleteOperation::empty();
260 }
261
262 let selection = self.selection();
264 let deleted = text.slice(selection.start..selection.end).into();
265 let diff = OpaqueDiff::new(
266 text.char_to_byte(selection.start),
267 text.char_to_byte(selection.end) - text.char_to_byte(selection.start),
268 0,
269 selection.start,
270 selection.end - selection.start,
271 0,
272 );
273 text.remove(selection.start..selection.end);
274
275 let grapheme_start = cmp::min(
277 self.range.start,
278 text.prev_grapheme_boundary(text.len_chars()),
279 );
280 let grapheme_end = text.next_grapheme_boundary(grapheme_start);
281
282 *self = Cursor::with_range(grapheme_start..grapheme_end);
283
284 DeleteOperation { diff, deleted }
285 }
286
287 pub fn sync(&mut self, current_text: &Rope, new_text: &Rope) {
288 let current_line = current_text.char_to_line(self.range.start);
289 let current_line_offset = self.range.start - current_text.line_to_char(current_line);
290
291 let new_line = cmp::min(current_line, new_text.len_lines().saturating_sub(1));
292 let new_line_offset = cmp::min(
293 current_line_offset,
294 new_text.line(new_line).len_chars().saturating_sub(1),
295 );
296 let grapheme_end =
297 new_text.next_grapheme_boundary(new_text.line_to_char(new_line) + new_line_offset);
298 let grapheme_start = new_text.prev_grapheme_boundary(grapheme_end);
299
300 *self = Cursor::with_range(grapheme_start..grapheme_end);
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use ropey::Rope;
307
308 use super::*;
309
310 fn text_with_cursor(text: impl Into<Rope>) -> (Rope, Cursor) {
311 let text = text.into();
312 let mut cursor = Cursor::new();
313 movement::move_horizontally(&text, &mut cursor, Direction::Backward, 1);
314 (text, cursor)
315 }
316
317 #[test]
318 fn sync_with_empty() {
319 let current_text = Rope::from("Buy a milk goat\nAt the market\n");
320 let new_text = Rope::from("");
321 let mut cursor = Cursor::new();
322 movement::move_horizontally(¤t_text, &mut cursor, Direction::Forward, 4);
323 cursor.sync(¤t_text, &new_text);
324 assert_eq!(Cursor::new(), cursor);
325 }
326
327 #[test]
329 fn delete_forward_at_the_end() {
330 let (mut text, mut cursor) = text_with_cursor(TEXT);
331 let expected = text.clone();
332 movement::move_to_end_of_buffer(&text, &mut cursor);
333 cursor.delete_forward(&mut text);
334 assert_eq!(expected, text);
335 }
336
337 #[test]
338 fn delete_forward_empty_text() {
339 let (mut text, mut cursor) = text_with_cursor("");
340 cursor.delete_forward(&mut text);
341 assert_eq!(cursor, Cursor::new());
342 }
343
344 #[test]
345 fn delete_forward_at_the_begining() {
346 let (mut text, mut cursor) = text_with_cursor("// Hello world!\n\n");
347 let expected = Rope::from("Hello world!\n\n");
348 cursor.delete_forward(&mut text);
349 cursor.delete_forward(&mut text);
350 cursor.delete_forward(&mut text);
351 assert_eq!(expected, text);
352 }
353
354 #[test]
356 fn delete_backward_at_the_end() {
357 let (mut text, mut cursor) = text_with_cursor("// Hello world!\n");
358 movement::move_to_end_of_buffer(&text, &mut cursor);
359 cursor.delete_backward(&mut text);
360 assert_eq!(Rope::from("// Hello world!"), text);
361 cursor.delete_backward(&mut text);
362 assert_eq!(Rope::from("// Hello world"), text);
363 }
364
365 #[test]
366 fn delete_backward_empty_text() {
367 let (mut text, mut cursor) = text_with_cursor("");
368 cursor.delete_backward(&mut text);
369 assert_eq!(cursor, Cursor::new());
370 }
371
372 #[test]
373 fn delete_backward_at_the_begining() {
374 let (mut text, mut cursor) = text_with_cursor("// Hello world!\n");
375 let expected = text.clone();
376 cursor.delete_backward(&mut text);
377 assert_eq!(expected, text);
378 }
379
380 const TEXT: &str = r#"
381Basic Latin
382 ! " # $ % & ' ( ) *+,-./012ABCDEFGHI` a m t u v z { | } ~
383CJK
384 豈 更 車 Ⅷ
385"#;
386}