1use ropey::Rope;
2use std::fs::OpenOptions;
3use std::io::BufReader;
4use std::ops::Range;
5use unicode_width::UnicodeWidthStr;
6
7use crate::position::Position;
8
9pub const JUMP_MATCHES: [char; 100] = make_matches();
10
11const fn make_matches() -> [char; 100] {
12 [
13 '^', '@', '|', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
14 '[', ']', '{', '`', '}', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<',
15 '=', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
16 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
17 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
18 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ',
19 ]
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum CharType {
24 Letter,
25 Punctuation,
26 Number,
27 NewLine,
28 WhiteSpace,
29 None,
30}
31impl From<char> for CharType {
32 fn from(c: char) -> Self {
33 match c {
34 _ if c.is_ascii_alphabetic() => Self::Letter,
35 _ if c.is_ascii_punctuation() => Self::Punctuation,
36 _ if c.is_ascii_digit() => Self::Number,
37 _ if c.is_ascii_whitespace() => Self::WhiteSpace,
38 '\n' => Self::NewLine,
39 _ => Self::None,
40 }
41 }
42}
43
44impl From<&str> for CharType {
45 fn from(s: &str) -> Self {
46 match s.parse::<char>().ok() {
47 Some(c) => Self::from(c),
48 None => Self::None,
49 }
50 }
51}
52
53#[derive(Debug, Default, Clone, PartialEq, Eq)]
54pub struct Buffer {
55 inner: Rope,
56 name: Option<String>,
57}
58
59impl Buffer {
60 #[must_use]
62 pub fn new() -> Self {
63 Self {
64 inner: Rope::new(),
65 name: None,
66 }
67 }
68
69 #[must_use]
71 pub fn from_path(filename: &str) -> Self {
72 let rope = from_path(filename);
73 Self {
74 inner: rope,
75 name: Some(filename.to_string()),
76 }
77 }
78
79 #[must_use]
81 pub fn new_str(text: &str) -> Self {
82 Self {
83 inner: Rope::from(text),
84 name: None,
85 }
86 }
87
88 #[must_use]
89 pub fn name(&self) -> &Option<String> {
90 &self.name
91 }
92
93 #[must_use]
94 pub fn idx_of_position(&self, pos: &Position) -> usize {
95 self.inner.line_to_char(pos.as_usize_y()) + pos.as_usize_x()
96 }
97
98 #[must_use]
99 pub fn char_at_pos(&self, pos: &Position) -> char {
100 self.inner.char(self.idx_of_position(pos))
101 }
102
103 #[must_use]
104 pub fn line(&self, y: usize) -> String {
105 self.inner.line(y).chars().collect::<String>()
106 }
107
108 #[must_use]
109 pub fn line_len(&self, y: usize) -> usize {
110 let line = self.inner.line(y).chars().collect::<String>();
111 UnicodeWidthStr::width(line.as_str())
112 }
113
114 #[must_use]
115 pub fn len_chars(&self) -> usize {
116 self.inner.len_chars()
117 }
118
119 #[must_use]
120 pub fn len_lines(&self) -> usize {
121 self.inner.len_lines().saturating_sub(2)
122 }
123
124 #[must_use]
125 pub fn line_to_char(&self, line: usize) -> usize {
126 self.inner.line_to_char(line)
127 }
128
129 #[must_use]
130 pub fn char_to_line(&self, line: usize) -> usize {
131 self.inner.char_to_line(line)
132 }
133
134 pub fn insert_char(&mut self, idx: usize, c: char) {
135 self.inner.insert_char(idx, c);
136 }
137
138 pub fn remove(&mut self, range: Range<usize>) {
139 self.inner.remove(range);
140 }
141
142 #[must_use]
143 pub fn contents(&self) -> String {
144 self.inner.chars().collect::<String>()
145 }
146
147 pub fn clear(&mut self) {
148 self.inner = Rope::new();
149 }
150
151 #[must_use]
152 pub fn on_screen(&self, top: usize, bottom: usize) -> String {
153 let top_line = top;
154 let bottom_line = bottom;
155 self.inner
156 .lines_at(top_line)
157 .enumerate()
158 .filter(|(i, _)| *i < bottom_line)
159 .map(|(_, s)| s.to_string())
160 .collect::<String>()
161 }
162
163 pub fn write_to<T: std::io::Write>(&self, writer: T) -> std::io::Result<()> {
164 self.inner.write_to(writer)?;
165 Ok(())
166 }
167
168 #[must_use]
169 pub fn next_jump_idx(&self, pos: &Position) -> Option<usize> {
170 let (x, y) = pos.as_usize();
172 let result: Vec<(usize, CharType)> = self.line(y).as_str()[x..]
173 .match_indices(&JUMP_MATCHES[..])
174 .map(|(i, c)| (i, c.into()))
175 .collect();
176 let possible_jumps = word_indices(&result);
177 possible_jumps
178 .get(1)
179 .and_then(|w| w.first())
180 .map(|(i, _)| *i + x)
181 }
182
183 #[must_use]
184 pub fn prev_jump_idx(&self, pos: &Position) -> Option<usize> {
185 let (x, y) = pos.as_usize();
187 let result: Vec<(usize, CharType)> = self.line(y).as_str()[..x]
188 .rmatch_indices(&JUMP_MATCHES[..])
189 .map(|(i, c)| (i, c.into()))
190 .collect();
191 let possible_jumps = word_indices(&result);
192 let idx = possible_jumps
193 .get(0)
194 .and_then(|w| w.last())
195 .map_or(false, |(_, i)| i == &CharType::WhiteSpace) as usize;
196 possible_jumps
197 .get(idx)
198 .and_then(|w| w.last())
199 .map(|(i, _)| *i)
200 }
201}
202
203#[must_use]
204pub fn from_path(path: &str) -> Rope {
205 let file = OpenOptions::new()
206 .read(true)
207 .write(true)
208 .create(true)
209 .open(path)
210 .expect("Problem opening the file");
211
212 Rope::from_reader(BufReader::new(file)).expect("Failed to open file.")
213}
214
215fn word_indices(items: &[(usize, CharType)]) -> Vec<Vec<(usize, CharType)>> {
216 let mut stream = items.iter().peekable();
218 let mut word_loc = Vec::new();
219 if let Some(f) = stream.next() {
220 word_loc.push(vec![*f]);
221 } else {
222 return word_loc;
223 }
224
225 while let Some(current) = stream.next() {
226 if current.1 == CharType::WhiteSpace {
227 if let Some(f) = word_loc.last() {
228 if !f.is_empty() {
229 word_loc.push(Vec::new());
230 }
231 }
232 continue;
233 }
234 if let Some(last_word) = word_loc.last_mut() {
235 if let Some(last_char) = last_word.last() {
236 if current.1 != last_char.1 {
237 word_loc.push(vec![*current]);
238 } else if let Some(next) = stream.peek() {
239 if next.1 != current.1 {
240 last_word.push(*current);
241 }
242 } else {
243 last_word.push(*current);
244 }
245 } else {
246 last_word.push(*current);
247 }
248 }
249 }
250 word_loc
251}
252
253#[test]
254fn test_buffer_len() {
255 use ropey::Rope;
256 let rope = Rope::from("0\n1\n2\n3\n4\n5\n"); assert_eq!(rope.len_lines(), 7);
258}