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
#![deny(missing_docs)]
/*!
Utilities to simplify text editing when implementing text editors.
**/

#[derive(Clone)]
/// The text line represents editable text lines.
pub struct TextLine {
    text: String,
    indices: Vec<usize>,
}

use std::ops::Range;

impl TextLine {
    /// Creates a new empty text line.
    pub fn new() -> Self {
        Self {
            text: String::new(),
            indices: Vec::new(),
        }
    }

    fn enable_indices(&mut self) {
        let mut index = 0;
        for c in self.text.chars() {
            index += c.len_utf8() - 1;
            self.indices.push(index);
        }
    }

    fn refresh_indices(&mut self) {
        if !self.text.is_ascii() {
            self.enable_indices();
        }
    }

    /// Creates a text line from a `String`.
    pub fn from_string(text: String) -> Self {
        let mut result = Self {
            text,
            indices: Vec::new(),
        };
        result.refresh_indices();
        result
    }

    /// Creates a text line from a `str` reference.
    pub fn from_str(text: &str) -> Self {
        Self::from_string(text.to_string())
    }

    /// Checks if the line is empty.
    pub fn is_empty(&self) -> bool {
        self.text.is_empty()
    }

    /// Returns the length of the text line.
    pub fn len(&self) -> usize {
        if self.indices.is_empty() {
            self.text.len()
        } else {
            self.indices.len()
        }
    }

    /// Returns the text of the text line as `String`.
    pub fn to_string(&self) -> String {
        self.text.clone()
    }

    /// Returns the text of the text line as a `str` reference.
    pub fn as_str(&self) -> &str {
        &self.text
    }

    /// Converts the character index to the string index.    
    pub fn string_index(&self, index: usize) -> usize {
        if !self.indices.is_empty() && index > 0 {
            self.indices[index - 1] + index
        } else {
            index
        }
    }

    /// Returns the char at the specified position.
    pub fn char_at(&self, at: usize) -> char {
        self.char_at_checked(at).unwrap()
    }

    fn char_at_checked(&self, at: usize) -> Option<char> {
        self.text[self.string_index(at)..].chars().next()
    }

    /// Inserts a new char into text line.
    pub fn insert(&mut self, index: usize, c: char) {
        self.text.insert(self.string_index(index), c);
        self.indices.clear();
        self.refresh_indices();
    }

    /// Removes a char from text line.
    pub fn remove(&mut self, index: usize) -> char {
        let result = self.text.remove(self.string_index(index));
        self.indices.clear();
        self.refresh_indices();
        result
    }

    /// Removes the specified range from the text line.
    pub fn remove_range(&mut self, range: Range<usize>) {
        self.text.replace_range(
            self.string_index(range.start)..self.string_index(range.end),
            "",
        );
        self.indices.clear();
        self.refresh_indices();
    }

    /// Splits a text line into two.
    pub fn split(&mut self, index: usize) -> Self {
        let mut result = Self {
            text: self.text.split_off(self.string_index(index)),
            indices: Vec::new(),
        };
        self.indices.clear();
        self.refresh_indices();
        result.refresh_indices();
        result
    }

    /// Joins two text lines into one.
    pub fn join(&mut self, other: Self) {
        self.text.extend(other.text.chars());
        self.indices.clear();
        self.refresh_indices();
    }
}

mod cursor;
mod editing;