⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/devtools/ide/
editor.rs

1//! Text Editor with Gap Buffer
2//!
3//! Core text editing engine with gap buffer data structure, multiple buffer
4//! support, undo/redo history, and syntax highlighting integration.
5
6use alloc::{
7    string::{String, ToString},
8    vec,
9    vec::Vec,
10};
11
12/// Gap buffer for efficient text editing
13pub(crate) struct GapBuffer {
14    buf: Vec<u8>,
15    gap_start: usize,
16    gap_end: usize,
17}
18
19impl Default for GapBuffer {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl GapBuffer {
26    const INITIAL_GAP: usize = 64;
27
28    pub(crate) fn new() -> Self {
29        Self {
30            buf: vec![0; Self::INITIAL_GAP],
31            gap_start: 0,
32            gap_end: Self::INITIAL_GAP,
33        }
34    }
35
36    pub(crate) fn from_text(text: &str) -> Self {
37        let bytes = text.as_bytes();
38        let gap_size = Self::INITIAL_GAP;
39        let mut buf = Vec::with_capacity(bytes.len() + gap_size);
40        buf.extend_from_slice(bytes);
41        buf.resize(bytes.len() + gap_size, 0);
42
43        Self {
44            buf,
45            gap_start: bytes.len(),
46            gap_end: bytes.len() + gap_size,
47        }
48    }
49
50    pub(crate) fn len(&self) -> usize {
51        self.buf.len() - self.gap_len()
52    }
53
54    pub(crate) fn is_empty(&self) -> bool {
55        self.len() == 0
56    }
57
58    fn gap_len(&self) -> usize {
59        self.gap_end - self.gap_start
60    }
61
62    fn move_gap_to(&mut self, pos: usize) {
63        if pos == self.gap_start {
64            return;
65        }
66        if pos < self.gap_start {
67            let count = self.gap_start - pos;
68            let src = pos;
69            let dst = self.gap_end - count;
70            self.buf.copy_within(src..src + count, dst);
71            self.gap_start = pos;
72            self.gap_end -= count;
73        } else {
74            let count = pos - self.gap_start;
75            let src = self.gap_end;
76            let dst = self.gap_start;
77            self.buf.copy_within(src..src + count, dst);
78            self.gap_start += count;
79            self.gap_end += count;
80        }
81    }
82
83    fn ensure_gap(&mut self, needed: usize) {
84        if self.gap_len() >= needed {
85            return;
86        }
87        let extra = core::cmp::max(needed * 2, 64);
88        let old_gap_end = self.gap_end;
89        let after_gap = self.buf.len() - old_gap_end;
90
91        self.buf.resize(self.buf.len() + extra, 0);
92        // Move content after gap
93        if after_gap > 0 {
94            self.buf
95                .copy_within(old_gap_end..old_gap_end + after_gap, old_gap_end + extra);
96        }
97        self.gap_end += extra;
98    }
99
100    /// Insert a character at position
101    pub(crate) fn insert(&mut self, pos: usize, ch: u8) {
102        self.move_gap_to(pos);
103        self.ensure_gap(1);
104        self.buf[self.gap_start] = ch;
105        self.gap_start += 1;
106    }
107
108    /// Insert a string at position
109    pub(crate) fn insert_str(&mut self, pos: usize, text: &str) {
110        self.move_gap_to(pos);
111        self.ensure_gap(text.len());
112        self.buf[self.gap_start..self.gap_start + text.len()].copy_from_slice(text.as_bytes());
113        self.gap_start += text.len();
114    }
115
116    /// Delete a character at position
117    pub(crate) fn delete(&mut self, pos: usize) -> Option<u8> {
118        if pos >= self.len() {
119            return None;
120        }
121        self.move_gap_to(pos);
122        let ch = self.buf[self.gap_end];
123        self.gap_end += 1;
124        Some(ch)
125    }
126
127    /// Delete a range of characters
128    pub(crate) fn delete_range(&mut self, start: usize, end: usize) -> usize {
129        if start >= end || start >= self.len() {
130            return 0;
131        }
132        let end = core::cmp::min(end, self.len());
133        self.move_gap_to(start);
134        let count = end - start;
135        self.gap_end += count;
136        count
137    }
138
139    /// Get character at position
140    pub(crate) fn char_at(&self, pos: usize) -> Option<u8> {
141        if pos >= self.len() {
142            return None;
143        }
144        let actual_pos = if pos < self.gap_start {
145            pos
146        } else {
147            pos + self.gap_len()
148        };
149        Some(self.buf[actual_pos])
150    }
151
152    /// Convert to String
153    pub(crate) fn to_text(&self) -> String {
154        let mut result = Vec::with_capacity(self.len());
155        for i in 0..self.gap_start {
156            result.push(self.buf[i]);
157        }
158        for i in self.gap_end..self.buf.len() {
159            result.push(self.buf[i]);
160        }
161        String::from_utf8(result).unwrap_or_default()
162    }
163
164    /// Get a line by number (0-based)
165    pub(crate) fn line(&self, line_num: usize) -> Option<String> {
166        let text = self.to_text();
167        text.lines().nth(line_num).map(|s| s.to_string())
168    }
169
170    /// Count lines
171    pub(crate) fn line_count(&self) -> usize {
172        let text = self.to_text();
173        text.lines().count().max(1)
174    }
175}
176
177/// Undo/redo operation
178#[derive(Debug, Clone)]
179enum EditOp {
180    Insert { pos: usize, text: String },
181    Delete { pos: usize, text: String },
182}
183
184/// Editor buffer (file being edited)
185pub(crate) struct EditorBuffer {
186    pub(crate) name: String,
187    pub(crate) gap_buf: GapBuffer,
188    pub(crate) cursor_pos: usize,
189    pub(crate) cursor_line: usize,
190    pub(crate) cursor_col: usize,
191    pub(crate) modified: bool,
192    undo_stack: Vec<EditOp>,
193    redo_stack: Vec<EditOp>,
194    pub(crate) scroll_top: usize,
195}
196
197impl EditorBuffer {
198    pub(crate) fn new(name: &str) -> Self {
199        Self {
200            name: name.to_string(),
201            gap_buf: GapBuffer::new(),
202            cursor_pos: 0,
203            cursor_line: 0,
204            cursor_col: 0,
205            modified: false,
206            undo_stack: Vec::new(),
207            redo_stack: Vec::new(),
208            scroll_top: 0,
209        }
210    }
211
212    pub(crate) fn from_text(name: &str, text: &str) -> Self {
213        Self {
214            name: name.to_string(),
215            gap_buf: GapBuffer::from_text(text),
216            cursor_pos: 0,
217            cursor_line: 0,
218            cursor_col: 0,
219            modified: false,
220            undo_stack: Vec::new(),
221            redo_stack: Vec::new(),
222            scroll_top: 0,
223        }
224    }
225
226    pub(crate) fn insert_char(&mut self, ch: u8) {
227        self.gap_buf.insert(self.cursor_pos, ch);
228        let text = String::from(ch as char);
229        self.undo_stack.push(EditOp::Insert {
230            pos: self.cursor_pos,
231            text,
232        });
233        self.redo_stack.clear();
234        self.cursor_pos += 1;
235        self.modified = true;
236        self.update_cursor_pos();
237    }
238
239    pub(crate) fn insert_text(&mut self, text: &str) {
240        self.gap_buf.insert_str(self.cursor_pos, text);
241        self.undo_stack.push(EditOp::Insert {
242            pos: self.cursor_pos,
243            text: text.to_string(),
244        });
245        self.redo_stack.clear();
246        self.cursor_pos += text.len();
247        self.modified = true;
248        self.update_cursor_pos();
249    }
250
251    pub(crate) fn delete_char(&mut self) -> Option<u8> {
252        if self.cursor_pos >= self.gap_buf.len() {
253            return None;
254        }
255        let ch = self.gap_buf.delete(self.cursor_pos)?;
256        self.undo_stack.push(EditOp::Delete {
257            pos: self.cursor_pos,
258            text: String::from(ch as char),
259        });
260        self.redo_stack.clear();
261        self.modified = true;
262        Some(ch)
263    }
264
265    pub(crate) fn backspace(&mut self) -> Option<u8> {
266        if self.cursor_pos == 0 {
267            return None;
268        }
269        self.cursor_pos -= 1;
270        self.delete_char()
271    }
272
273    pub(crate) fn undo(&mut self) -> bool {
274        let op = match self.undo_stack.pop() {
275            Some(op) => op,
276            None => return false,
277        };
278
279        match &op {
280            EditOp::Insert { pos, text } => {
281                self.gap_buf.delete_range(*pos, pos + text.len());
282                self.cursor_pos = *pos;
283                self.redo_stack.push(op);
284            }
285            EditOp::Delete { pos, text } => {
286                self.gap_buf.insert_str(*pos, text);
287                self.cursor_pos = pos + text.len();
288                self.redo_stack.push(op);
289            }
290        }
291
292        self.update_cursor_pos();
293        true
294    }
295
296    pub(crate) fn redo(&mut self) -> bool {
297        let op = match self.redo_stack.pop() {
298            Some(op) => op,
299            None => return false,
300        };
301
302        match &op {
303            EditOp::Insert { pos, text } => {
304                self.gap_buf.insert_str(*pos, text);
305                self.cursor_pos = pos + text.len();
306                self.undo_stack.push(op);
307            }
308            EditOp::Delete { pos, text: _ } => {
309                self.gap_buf.delete(self.cursor_pos);
310                self.cursor_pos = *pos;
311                self.undo_stack.push(op);
312            }
313        }
314
315        self.update_cursor_pos();
316        true
317    }
318
319    fn update_cursor_pos(&mut self) {
320        let text = self.gap_buf.to_text();
321        let before = &text[..self.cursor_pos.min(text.len())];
322        self.cursor_line = before.matches('\n').count();
323        self.cursor_col = before
324            .rfind('\n')
325            .map_or(before.len(), |p| before.len() - p - 1);
326    }
327
328    pub(crate) fn content(&self) -> String {
329        self.gap_buf.to_text()
330    }
331
332    pub(crate) fn line_count(&self) -> usize {
333        self.gap_buf.line_count()
334    }
335}
336
337/// Multi-buffer editor
338pub(crate) struct Editor {
339    buffers: Vec<EditorBuffer>,
340    active: usize,
341}
342
343impl Default for Editor {
344    fn default() -> Self {
345        Self::new()
346    }
347}
348
349impl Editor {
350    pub(crate) fn new() -> Self {
351        Self {
352            buffers: Vec::new(),
353            active: 0,
354        }
355    }
356
357    pub(crate) fn open(&mut self, name: &str, content: &str) -> usize {
358        let idx = self.buffers.len();
359        self.buffers.push(EditorBuffer::from_text(name, content));
360        self.active = idx;
361        idx
362    }
363
364    pub(crate) fn close(&mut self, idx: usize) -> bool {
365        if idx >= self.buffers.len() {
366            return false;
367        }
368        self.buffers.remove(idx);
369        if self.active >= self.buffers.len() && !self.buffers.is_empty() {
370            self.active = self.buffers.len() - 1;
371        }
372        true
373    }
374
375    pub(crate) fn active_buffer(&self) -> Option<&EditorBuffer> {
376        self.buffers.get(self.active)
377    }
378
379    pub(crate) fn active_buffer_mut(&mut self) -> Option<&mut EditorBuffer> {
380        self.buffers.get_mut(self.active)
381    }
382
383    pub(crate) fn switch_to(&mut self, idx: usize) -> bool {
384        if idx < self.buffers.len() {
385            self.active = idx;
386            true
387        } else {
388            false
389        }
390    }
391
392    pub(crate) fn buffer_count(&self) -> usize {
393        self.buffers.len()
394    }
395}
396
397// ---------------------------------------------------------------------------
398// Tests
399// ---------------------------------------------------------------------------
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404
405    #[test]
406    fn test_gap_buffer_new() {
407        let buf = GapBuffer::new();
408        assert_eq!(buf.len(), 0);
409        assert!(buf.is_empty());
410    }
411
412    #[test]
413    fn test_gap_buffer_insert() {
414        let mut buf = GapBuffer::new();
415        buf.insert(0, b'h');
416        buf.insert(1, b'i');
417        assert_eq!(buf.len(), 2);
418        assert_eq!(buf.to_text(), "hi");
419    }
420
421    #[test]
422    fn test_gap_buffer_insert_str() {
423        let mut buf = GapBuffer::new();
424        buf.insert_str(0, "hello");
425        assert_eq!(buf.to_text(), "hello");
426        assert_eq!(buf.len(), 5);
427    }
428
429    #[test]
430    fn test_gap_buffer_delete() {
431        let mut buf = GapBuffer::from_text("hello");
432        let ch = buf.delete(0);
433        assert_eq!(ch, Some(b'h'));
434        assert_eq!(buf.to_text(), "ello");
435    }
436
437    #[test]
438    fn test_gap_buffer_delete_range() {
439        let mut buf = GapBuffer::from_text("hello world");
440        let count = buf.delete_range(5, 11);
441        assert_eq!(count, 6);
442        assert_eq!(buf.to_text(), "hello");
443    }
444
445    #[test]
446    fn test_gap_buffer_char_at() {
447        let buf = GapBuffer::from_text("abc");
448        assert_eq!(buf.char_at(0), Some(b'a'));
449        assert_eq!(buf.char_at(2), Some(b'c'));
450        assert_eq!(buf.char_at(3), None);
451    }
452
453    #[test]
454    fn test_gap_buffer_line() {
455        let buf = GapBuffer::from_text("line1\nline2\nline3");
456        assert_eq!(buf.line(0), Some("line1".to_string()));
457        assert_eq!(buf.line(1), Some("line2".to_string()));
458        assert_eq!(buf.line(2), Some("line3".to_string()));
459    }
460
461    #[test]
462    fn test_gap_buffer_line_count() {
463        let buf = GapBuffer::from_text("a\nb\nc");
464        assert_eq!(buf.line_count(), 3);
465    }
466
467    #[test]
468    fn test_editor_buffer_insert() {
469        let mut buf = EditorBuffer::new("test.txt");
470        buf.insert_text("hello");
471        assert_eq!(buf.content(), "hello");
472        assert!(buf.modified);
473    }
474
475    #[test]
476    fn test_editor_buffer_undo_redo() {
477        let mut buf = EditorBuffer::new("test.txt");
478        buf.insert_text("hello");
479        assert_eq!(buf.content(), "hello");
480
481        assert!(buf.undo());
482        assert_eq!(buf.content(), "");
483
484        assert!(buf.redo());
485        assert_eq!(buf.content(), "hello");
486    }
487
488    #[test]
489    fn test_editor_multi_buffer() {
490        let mut editor = Editor::new();
491        editor.open("a.txt", "file A");
492        editor.open("b.txt", "file B");
493
494        assert_eq!(editor.buffer_count(), 2);
495        assert_eq!(editor.active_buffer().unwrap().name, "b.txt");
496
497        editor.switch_to(0);
498        assert_eq!(editor.active_buffer().unwrap().name, "a.txt");
499    }
500
501    #[test]
502    fn test_editor_close() {
503        let mut editor = Editor::new();
504        editor.open("a.txt", "");
505        editor.open("b.txt", "");
506        assert!(editor.close(0));
507        assert_eq!(editor.buffer_count(), 1);
508    }
509}