editor_core/
line_index.rs1use crate::storage::Piece;
6use ropey::Rope;
7
8#[derive(Debug, Clone)]
10pub struct LineMetadata {
11 pub pieces: Vec<Piece>,
13 pub is_pure_ascii: bool,
15 pub byte_length: usize,
17 pub char_count: usize,
19}
20
21impl LineMetadata {
22 pub fn new() -> Self {
24 Self {
25 pieces: Vec::new(),
26 is_pure_ascii: true,
27 byte_length: 0,
28 char_count: 0,
29 }
30 }
31
32 pub fn from_text(text: &str) -> Self {
34 let is_pure_ascii = text.is_ascii();
35 Self {
36 pieces: Vec::new(),
37 is_pure_ascii,
38 byte_length: text.len(),
39 char_count: text.chars().count(),
40 }
41 }
42}
43
44impl Default for LineMetadata {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50pub struct LineIndex {
54 rope: Rope,
56}
57
58impl LineIndex {
59 pub fn new() -> Self {
61 Self { rope: Rope::new() }
62 }
63
64 pub fn from_text(text: &str) -> Self {
66 Self {
67 rope: Rope::from_str(text),
68 }
69 }
70
71 pub fn append_line(&mut self, line: LineMetadata) {
73 let current_len = self.rope.len_chars();
75
76 if current_len > 0 {
78 self.rope.insert(current_len, "\n");
79 }
80
81 let placeholder = "x".repeat(line.char_count);
84 self.rope.insert(self.rope.len_chars(), &placeholder);
85 }
86
87 pub fn insert_line(&mut self, line_number: usize, line: LineMetadata) {
89 if line_number >= self.rope.len_lines() {
90 self.append_line(line);
91 return;
92 }
93
94 let insert_pos = self.rope.line_to_char(line_number);
96
97 let placeholder = "x".repeat(line.char_count);
99 self.rope.insert(insert_pos, &placeholder);
100 self.rope.insert(insert_pos + line.char_count, "\n");
101 }
102
103 pub fn delete_line(&mut self, line_number: usize) {
105 if line_number >= self.rope.len_lines() {
106 return;
107 }
108
109 let start_char = self.rope.line_to_char(line_number);
110 let end_char = if line_number + 1 < self.rope.len_lines() {
111 self.rope.line_to_char(line_number + 1)
112 } else {
113 self.rope.len_chars()
114 };
115
116 self.rope.remove(start_char..end_char);
117 }
118
119 pub fn get_line(&self, line_number: usize) -> Option<LineMetadata> {
121 if line_number >= self.rope.len_lines() {
122 return None;
123 }
124
125 let line = self.rope.line(line_number);
126 let mut text = line.to_string();
127
128 if text.ends_with('\n') {
130 text.pop();
131 }
132 if text.ends_with('\r') {
133 text.pop();
134 }
135
136 Some(LineMetadata::from_text(&text))
137 }
138
139 pub fn get_line_mut(&mut self, line_number: usize) -> Option<&mut LineMetadata> {
141 let _line_num = line_number;
143 None
144 }
145
146 pub fn line_to_offset(&self, line_number: usize) -> usize {
148 if line_number == 0 {
149 return 0;
150 }
151
152 if line_number >= self.rope.len_lines() {
153 let newline_count = self.rope.len_lines().saturating_sub(1);
155 return self.rope.len_bytes().saturating_sub(newline_count);
156 }
157
158 self.rope
161 .line_to_byte(line_number)
162 .saturating_sub(line_number)
163 }
164
165 pub fn offset_to_line(&self, offset: usize) -> usize {
167 if offset == 0 {
168 return 0;
169 }
170
171 let mut low = 0;
174 let mut high = self.rope.len_lines();
175
176 while low < high {
177 let mid = (low + high) / 2;
178 let mid_offset = self.line_to_offset(mid);
179
180 if mid_offset < offset {
181 low = mid + 1;
182 } else if mid_offset > offset {
183 high = mid;
184 } else {
185 return mid;
186 }
187 }
188
189 low.saturating_sub(1)
190 .min(self.rope.len_lines().saturating_sub(1))
191 }
192
193 pub fn char_offset_to_position(&self, char_offset: usize) -> (usize, usize) {
195 let char_offset = char_offset.min(self.rope.len_chars());
196
197 let line_idx = self.rope.char_to_line(char_offset);
198 let line_start_char = self.rope.line_to_char(line_idx);
199 let char_in_line = char_offset - line_start_char;
200
201 (line_idx, char_in_line)
202 }
203
204 pub fn position_to_char_offset(&self, line: usize, column: usize) -> usize {
206 if line >= self.rope.len_lines() {
207 return self.rope.len_chars();
208 }
209
210 let line_start_char = self.rope.line_to_char(line);
211 let line_len = if line + 1 < self.rope.len_lines() {
212 self.rope.line_to_char(line + 1) - line_start_char - 1 } else {
214 self.rope.len_chars() - line_start_char
215 };
216
217 line_start_char + column.min(line_len)
218 }
219
220 pub fn line_count(&self) -> usize {
222 self.rope.len_lines()
223 }
224
225 pub fn byte_count(&self) -> usize {
227 self.rope.len_bytes()
228 }
229
230 pub fn char_count(&self) -> usize {
232 self.rope.len_chars()
233 }
234
235 pub fn insert(&mut self, char_offset: usize, text: &str) {
237 let char_offset = char_offset.min(self.rope.len_chars());
238 self.rope.insert(char_offset, text);
239 }
240
241 pub fn delete(&mut self, start_char: usize, len_chars: usize) {
243 let start_char = start_char.min(self.rope.len_chars());
244 let end_char = (start_char + len_chars).min(self.rope.len_chars());
245
246 if start_char < end_char {
247 self.rope.remove(start_char..end_char);
248 }
249 }
250
251 pub fn get_text(&self) -> String {
253 self.rope.to_string()
254 }
255
256 pub fn get_line_text(&self, line_number: usize) -> Option<String> {
258 if line_number >= self.rope.len_lines() {
259 return None;
260 }
261
262 let mut text = self.rope.line(line_number).to_string();
263
264 if text.ends_with('\n') {
266 text.pop();
267 }
268 if text.ends_with('\r') {
269 text.pop();
270 }
271
272 Some(text)
273 }
274}
275
276impl Default for LineIndex {
277 fn default() -> Self {
278 Self::new()
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn test_new_line_index() {
288 let index = LineIndex::new();
289 assert_eq!(index.line_count(), 1); assert_eq!(index.byte_count(), 0);
291 assert_eq!(index.char_count(), 0);
292 }
293
294 #[test]
295 fn test_from_text() {
296 let text = "Line 1\nLine 2\nLine 3";
297 let index = LineIndex::from_text(text);
298
299 assert_eq!(index.line_count(), 3);
300 assert_eq!(index.byte_count(), text.len());
301 assert_eq!(index.char_count(), text.chars().count());
302 }
303
304 #[test]
305 fn test_line_to_offset() {
306 let text = "First line\nSecond line\nThird line";
307 let index = LineIndex::from_text(text);
308
309 assert_eq!(index.line_to_offset(0), 0);
310 assert_eq!(index.line_to_offset(1), 10); assert_eq!(index.line_to_offset(2), 21); }
313
314 #[test]
315 fn test_offset_to_line() {
316 let text = "First line\nSecond line\nThird line";
317 let index = LineIndex::from_text(text);
318
319 assert_eq!(index.offset_to_line(0), 0);
320 assert_eq!(index.offset_to_line(5), 0);
321 assert_eq!(index.offset_to_line(11), 1);
322 assert_eq!(index.offset_to_line(23), 2);
323 }
324
325 #[test]
326 fn test_char_offset_to_position() {
327 let text = "ABC\nDEF\nGHI";
328 let index = LineIndex::from_text(text);
329
330 assert_eq!(index.char_offset_to_position(0), (0, 0)); assert_eq!(index.char_offset_to_position(2), (0, 2)); assert_eq!(index.char_offset_to_position(4), (1, 0)); assert_eq!(index.char_offset_to_position(8), (2, 0)); }
335
336 #[test]
337 fn test_position_to_char_offset() {
338 let text = "ABC\nDEF\nGHI";
339 let index = LineIndex::from_text(text);
340
341 assert_eq!(index.position_to_char_offset(0, 0), 0); assert_eq!(index.position_to_char_offset(0, 2), 2); assert_eq!(index.position_to_char_offset(1, 0), 4); assert_eq!(index.position_to_char_offset(2, 0), 8); }
346
347 #[test]
348 fn test_utf8_cjk() {
349 let text = "你好\n世界";
350 let index = LineIndex::from_text(text);
351
352 assert_eq!(index.line_count(), 2);
353 assert_eq!(index.byte_count(), text.len());
354 assert_eq!(index.char_count(), 5); assert_eq!(index.char_offset_to_position(0), (0, 0));
358 assert_eq!(index.char_offset_to_position(1), (0, 1));
359 assert_eq!(index.char_offset_to_position(3), (1, 0));
361 }
362
363 #[test]
364 fn test_get_line() {
365 let text = "Line 1\nLine 2\nLine 3";
366 let index = LineIndex::from_text(text);
367
368 let line0 = index.get_line(0);
369 assert!(line0.is_some());
370 let meta = line0.unwrap();
371 assert!(meta.is_pure_ascii);
372
373 let line_none = index.get_line(10);
374 assert!(line_none.is_none());
375 }
376
377 #[test]
378 fn test_insert_delete_lines() {
379 let mut index = LineIndex::from_text("Line 1\nLine 2");
380 assert_eq!(index.line_count(), 2);
381
382 index.delete_line(0);
383 assert_eq!(index.line_count(), 1);
384 }
385
386 #[test]
387 fn test_mixed_ascii_cjk() {
388 let text = "Hello 你好\nWorld 世界";
389 let index = LineIndex::from_text(text);
390
391 assert_eq!(index.line_count(), 2);
392 assert!(index.byte_count() > index.char_count());
393 }
394
395 #[test]
396 fn test_large_document() {
397 let mut lines = Vec::new();
398 for i in 0..10000 {
399 lines.push(format!("Line {}", i));
400 }
401 let text = lines.join("\n");
402
403 let index = LineIndex::from_text(&text);
404 assert_eq!(index.line_count(), 10000);
405
406 let line_5000 = index.get_line(5000);
408 assert!(line_5000.is_some());
409 }
410
411 #[test]
412 fn test_insert_text() {
413 let mut index = LineIndex::from_text("Hello World");
414
415 index.insert(6, "Beautiful ");
416 assert_eq!(index.get_text(), "Hello Beautiful World");
417 }
418
419 #[test]
420 fn test_delete_text() {
421 let mut index = LineIndex::from_text("Hello Beautiful World");
422
423 index.delete(6, 10); assert_eq!(index.get_text(), "Hello World");
425 }
426}