1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
use crate::buffer::operation::Operation;
use crate::buffer::{Buffer, Position, Range};
use std::clone::Clone;
use std::convert::Into;
use unicode_segmentation::UnicodeSegmentation;

/// A reversible buffer insert operation.
///
/// Inserts the provided content at the specified position. Tracks both, and reverses
/// the operation by calculating the content's start and end positions (range), relative
/// to its inserted location, and removing said range from the underlying buffer.
///
/// If the buffer is configured with a `change_callback`, it will be called with
/// the position of this operation when it is run or reversed.
#[derive(Clone)]
pub struct Insert {
    content: String,
    position: Position,
}

impl Operation for Insert {
    fn run(&mut self, buffer: &mut Buffer) {
        buffer
            .data
            .borrow_mut()
            .insert(&self.content, &self.position);

        // Run the change callback, if present.
        if let Some(ref callback) = buffer.change_callback {
            callback(self.position)
        }
    }

    // We need to calculate the range of the inserted content.
    // The start of the range corresponds to the cursor position at the time of the insert,
    // which we've stored. Finding the end of the range requires that we dig into the content.
    fn reverse(&mut self, buffer: &mut Buffer) {
        // The line count of the content tells us the line number for the end of the
        // range (just add the number of new lines to the starting line).
        let line_count = self.content.chars().filter(|&c| c == '\n').count() + 1;
        let end_line = self.position.line + line_count - 1;

        let end_offset = if line_count == 1 {
            // If there's only one line, the range starts and ends on the same line, and so its
            // offset needs to take the original insertion location into consideration.
            self.position.offset + self.content.graphemes(true).count()
        } else {
            // If there are multiple lines, the end of the range doesn't
            // need to consider the original insertion location.
            match self.content.split('\n').last() {
                Some(line) => line.graphemes(true).count(),
                None => return,
            }
        };

        // Now that we have the required info,
        // build the end position and total range.
        let end_position = Position {
            line: end_line,
            offset: end_offset,
        };
        let range = Range::new(self.position, end_position);

        // Remove the content we'd previously inserted.
        buffer.data.borrow_mut().delete(&range);

        // Run the change callback, if present.
        if let Some(ref callback) = buffer.change_callback {
            callback(self.position)
        }
    }

    fn clone_operation(&self) -> Box<dyn Operation> {
        Box::new(self.clone())
    }
}

impl Insert {
    /// Creates a new empty insert operation.
    pub fn new(content: String, position: Position) -> Insert {
        Insert { content, position }
    }
}

impl Buffer {
    /// Inserts `data` into the buffer at the cursor position.
    ///
    /// # Examples
    ///
    /// ```
    /// use scribe::Buffer;
    ///
    /// let mut buffer = Buffer::new();
    /// buffer.insert("scribe");
    /// assert_eq!(buffer.data(), "scribe");
    /// ```
    pub fn insert<T: Into<String>>(&mut self, data: T) {
        // Build and run an insert operation.
        let mut op = Insert::new(data.into(), self.cursor.position);
        op.run(self);

        // Store the operation in the history
        // object so that it can be undone.
        match self.operation_group {
            Some(ref mut group) => group.add(Box::new(op)),
            None => self.history.add(Box::new(op)),
        };
    }
}

#[cfg(test)]
mod tests {
    use super::Insert;
    use crate::buffer::operation::Operation;
    use crate::buffer::position::Position;
    use crate::buffer::Buffer;
    use std::cell::RefCell;
    use std::rc::Rc;

    #[test]
    fn run_and_reverse_add_and_remove_content_without_newlines_at_cursor_position() {
        // Set up a buffer with some data.
        let mut buffer = Buffer::new();
        buffer.insert("something");

        // Set up a position pointing to the end of the buffer's contents.
        let insert_position = Position { line: 0, offset: 9 };

        // Create the insert operation and run it.
        let mut insert_operation = Insert::new(" else".to_string(), insert_position);
        insert_operation.run(&mut buffer);

        assert_eq!(buffer.data(), "something else");

        insert_operation.reverse(&mut buffer);

        assert_eq!(buffer.data(), "something");
    }

    #[test]
    fn run_and_reverse_add_and_remove_content_with_newlines_at_cursor_position() {
        // Set up a buffer with some data.
        let mut buffer = Buffer::new();
        buffer.insert("\n something");

        // Set up a position pointing to the end of the buffer's contents.
        let insert_position = Position {
            line: 1,
            offset: 10,
        };

        // Create the insert operation and run it.
        //
        // NOTE: The newline character ensures that the operation doesn't use a naive
        //       algorithm based purely on the content length.
        let mut insert_operation = Insert::new("\n else\n entirely".to_string(), insert_position);
        insert_operation.run(&mut buffer);

        assert_eq!(buffer.data(), "\n something\n else\n entirely");

        insert_operation.reverse(&mut buffer);

        assert_eq!(buffer.data(), "\n something");
    }

    #[test]
    fn reverse_removes_a_newline() {
        // Set up a buffer with some data.
        let mut buffer = Buffer::new();
        let mut insert_operation = Insert::new("\n".to_string(), Position { line: 0, offset: 0 });
        insert_operation.run(&mut buffer);
        assert_eq!(buffer.data(), "\n");

        insert_operation.reverse(&mut buffer);
        assert_eq!(buffer.data(), "");
    }

    #[test]
    fn reverse_correctly_removes_line_ranges() {
        // Set up a buffer with some data.
        let mut buffer = Buffer::new();
        buffer.insert("scribe\nlibrary\n");

        let mut insert_operation =
            Insert::new("editor\n".to_string(), Position { line: 1, offset: 0 });
        insert_operation.run(&mut buffer);
        assert_eq!(buffer.data(), "scribe\neditor\nlibrary\n");

        insert_operation.reverse(&mut buffer);
        assert_eq!(buffer.data(), "scribe\nlibrary\n");
    }

    #[test]
    fn reverse_correctly_removes_single_line_content_with_graphemes() {
        // Set up a buffer with some data.
        let mut buffer = Buffer::new();
        buffer.insert("scribe\nlibrary");

        let mut insert_operation =
            Insert::new("नी editor ".to_string(), Position { line: 1, offset: 0 });
        insert_operation.run(&mut buffer);
        assert_eq!(buffer.data(), "scribe\nनी editor library");

        insert_operation.reverse(&mut buffer);
        assert_eq!(buffer.data(), "scribe\nlibrary");
    }

    #[test]
    fn reverse_correctly_removes_multi_line_content_with_graphemes() {
        // Set up a buffer with some data.
        let mut buffer = Buffer::new();
        buffer.insert("scribe\nlibrary");

        let mut insert_operation =
            Insert::new("\nनी editor".to_string(), Position { line: 0, offset: 6 });
        insert_operation.run(&mut buffer);
        assert_eq!(buffer.data(), "scribe\nनी editor\nlibrary");

        insert_operation.reverse(&mut buffer);
        assert_eq!(buffer.data(), "scribe\nlibrary");
    }

    #[test]
    fn run_calls_change_callback_with_position() {
        // Set up a buffer with some data.
        let mut buffer = Buffer::new();
        buffer.insert("something");

        // Set up a position pointing to the end of the buffer's contents.
        let insert_position = Position { line: 0, offset: 9 };

        // Create a position that we'll share with the callback.
        let tracked_position = Rc::new(RefCell::new(Position::new()));
        let callback_position = tracked_position.clone();

        // Set up the callback so that it updates the shared position.
        buffer.change_callback = Some(Box::new(move |change_position| {
            *callback_position.borrow_mut() = change_position
        }));

        // Create the insert operation and run it.
        let mut insert_operation = Insert::new(" else".to_string(), insert_position);
        insert_operation.run(&mut buffer);

        // Verify that the callback received the correct position.
        assert_eq!(*tracked_position.borrow(), Position { line: 0, offset: 9 });
    }

    #[test]
    fn reverse_calls_change_callback_with_position() {
        // Set up a buffer with some data.
        let mut buffer = Buffer::new();
        buffer.insert("something");

        // Set up a position pointing to the end of the buffer's contents.
        let insert_position = Position { line: 0, offset: 9 };

        // Create the insert operation and run it.
        let mut insert_operation = Insert::new(" else".to_string(), insert_position);
        insert_operation.run(&mut buffer);

        // Create a position that we'll share with the callback.
        let tracked_position = Rc::new(RefCell::new(Position::new()));
        let callback_position = tracked_position.clone();

        // Set up the callback so that it updates the shared position.
        buffer.change_callback = Some(Box::new(move |change_position| {
            *callback_position.borrow_mut() = change_position
        }));

        // Reverse the operation.
        insert_operation.reverse(&mut buffer);

        // Verify that the callback received the correct position.
        assert_eq!(*tracked_position.borrow(), Position { line: 0, offset: 9 });
    }
}