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
use buffer::operation::Operation;
use 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.
#[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);
    }

    // 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.clone(),
            end_position
        );

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

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

impl Insert {
    /// Creates a new empty insert operation.
    pub fn new(content: String, position: Position) -> Insert {
        Insert{ content: content, position: 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.clone());
        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 buffer::Buffer;
    use buffer::position::Position;
    use buffer::operation::Operation;

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