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
269 Some(text)
270 }
271}
272
273impl Default for LineIndex {
274 fn default() -> Self {
275 Self::new()
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_new_line_index() {
285 let index = LineIndex::new();
286 assert_eq!(index.line_count(), 1); assert_eq!(index.byte_count(), 0);
288 assert_eq!(index.char_count(), 0);
289 }
290
291 #[test]
292 fn test_from_text() {
293 let text = "Line 1\nLine 2\nLine 3";
294 let index = LineIndex::from_text(text);
295
296 assert_eq!(index.line_count(), 3);
297 assert_eq!(index.byte_count(), text.len());
298 assert_eq!(index.char_count(), text.chars().count());
299 }
300
301 #[test]
302 fn test_line_to_offset() {
303 let text = "First line\nSecond line\nThird line";
304 let index = LineIndex::from_text(text);
305
306 assert_eq!(index.line_to_offset(0), 0);
307 assert_eq!(index.line_to_offset(1), 10); assert_eq!(index.line_to_offset(2), 21); }
310
311 #[test]
312 fn test_offset_to_line() {
313 let text = "First line\nSecond line\nThird line";
314 let index = LineIndex::from_text(text);
315
316 assert_eq!(index.offset_to_line(0), 0);
317 assert_eq!(index.offset_to_line(5), 0);
318 assert_eq!(index.offset_to_line(11), 1);
319 assert_eq!(index.offset_to_line(23), 2);
320 }
321
322 #[test]
323 fn test_char_offset_to_position() {
324 let text = "ABC\nDEF\nGHI";
325 let index = LineIndex::from_text(text);
326
327 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)); }
332
333 #[test]
334 fn test_position_to_char_offset() {
335 let text = "ABC\nDEF\nGHI";
336 let index = LineIndex::from_text(text);
337
338 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); }
343
344 #[test]
345 fn test_utf8_cjk() {
346 let text = "你好\n世界";
347 let index = LineIndex::from_text(text);
348
349 assert_eq!(index.line_count(), 2);
350 assert_eq!(index.byte_count(), text.len());
351 assert_eq!(index.char_count(), 5); assert_eq!(index.char_offset_to_position(0), (0, 0));
355 assert_eq!(index.char_offset_to_position(1), (0, 1));
356 assert_eq!(index.char_offset_to_position(3), (1, 0));
358 }
359
360 #[test]
361 fn test_get_line() {
362 let text = "Line 1\nLine 2\nLine 3";
363 let index = LineIndex::from_text(text);
364
365 let line0 = index.get_line(0);
366 assert!(line0.is_some());
367 let meta = line0.unwrap();
368 assert!(meta.is_pure_ascii);
369
370 let line_none = index.get_line(10);
371 assert!(line_none.is_none());
372 }
373
374 #[test]
375 fn test_insert_delete_lines() {
376 let mut index = LineIndex::from_text("Line 1\nLine 2");
377 assert_eq!(index.line_count(), 2);
378
379 index.delete_line(0);
380 assert_eq!(index.line_count(), 1);
381 }
382
383 #[test]
384 fn test_mixed_ascii_cjk() {
385 let text = "Hello 你好\nWorld 世界";
386 let index = LineIndex::from_text(text);
387
388 assert_eq!(index.line_count(), 2);
389 assert!(index.byte_count() > index.char_count());
390 }
391
392 #[test]
393 fn test_large_document() {
394 let mut lines = Vec::new();
395 for i in 0..10000 {
396 lines.push(format!("Line {}", i));
397 }
398 let text = lines.join("\n");
399
400 let index = LineIndex::from_text(&text);
401 assert_eq!(index.line_count(), 10000);
402
403 let line_5000 = index.get_line(5000);
405 assert!(line_5000.is_some());
406 }
407
408 #[test]
409 fn test_insert_text() {
410 let mut index = LineIndex::from_text("Hello World");
411
412 index.insert(6, "Beautiful ");
413 assert_eq!(index.get_text(), "Hello Beautiful World");
414 }
415
416 #[test]
417 fn test_delete_text() {
418 let mut index = LineIndex::from_text("Hello Beautiful World");
419
420 index.delete(6, 10); assert_eq!(index.get_text(), "Hello World");
422 }
423}