1use unicode_width::UnicodeWidthStr;
2
3#[derive(Debug, Clone)]
4pub struct Editor {
5 content: String,
6 cursor: usize,
7 scroll_offset: usize,
8}
9
10impl Editor {
11 pub fn new() -> Self {
12 Self {
13 content: String::new(),
14 cursor: 0,
15 scroll_offset: 0,
16 }
17 }
18
19 pub fn with_content(content: String) -> Self {
20 let cursor = content.len();
21 Self {
22 content,
23 cursor,
24 scroll_offset: 0,
25 }
26 }
27
28 pub fn content(&self) -> &str {
29 &self.content
30 }
31
32 pub fn cursor(&self) -> usize {
33 self.cursor
34 }
35
36 pub fn scroll_offset(&self) -> usize {
37 self.scroll_offset
38 }
39
40 pub fn visual_cursor(&self) -> usize {
41 let before_cursor = &self.content[..self.cursor];
42 UnicodeWidthStr::width(before_cursor).saturating_sub(self.scroll_offset)
43 }
44
45 pub fn insert_char(&mut self, c: char) {
46 self.content.insert(self.cursor, c);
47 self.cursor += c.len_utf8();
48 }
49
50 pub fn delete_back(&mut self) {
51 if self.cursor > 0 {
52 let prev = self.prev_char_boundary();
53 self.content.drain(prev..self.cursor);
54 self.cursor = prev;
55 }
56 }
57
58 pub fn delete_forward(&mut self) {
59 if self.cursor < self.content.len() {
60 let next = self.next_char_boundary();
61 self.content.drain(self.cursor..next);
62 }
63 }
64
65 pub fn move_left(&mut self) {
66 if self.cursor > 0 {
67 self.cursor = self.prev_char_boundary();
68 }
69 }
70
71 pub fn move_right(&mut self) {
72 if self.cursor < self.content.len() {
73 self.cursor = self.next_char_boundary();
74 }
75 }
76
77 pub fn move_home(&mut self) {
78 self.cursor = 0;
79 self.scroll_offset = 0;
80 }
81
82 pub fn move_end(&mut self) {
83 self.cursor = self.content.len();
84 }
85
86 pub fn update_scroll(&mut self, visible_width: usize) {
87 let visual = UnicodeWidthStr::width(&self.content[..self.cursor]);
88 if visual < self.scroll_offset {
89 self.scroll_offset = visual;
90 } else if visual >= self.scroll_offset + visible_width {
91 self.scroll_offset = visual - visible_width + 1;
92 }
93 }
94
95 fn prev_char_boundary(&self) -> usize {
96 let mut pos = self.cursor - 1;
97 while !self.content.is_char_boundary(pos) {
98 pos -= 1;
99 }
100 pos
101 }
102
103 fn next_char_boundary(&self) -> usize {
104 let mut pos = self.cursor + 1;
105 while pos < self.content.len() && !self.content.is_char_boundary(pos) {
106 pos += 1;
107 }
108 pos
109 }
110}
111
112impl Default for Editor {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_insert_and_content() {
124 let mut editor = Editor::new();
125 editor.insert_char('h');
126 editor.insert_char('i');
127 assert_eq!(editor.content(), "hi");
128 assert_eq!(editor.cursor(), 2);
129 }
130
131 #[test]
132 fn test_delete_back() {
133 let mut editor = Editor::with_content("hello".to_string());
134 editor.delete_back();
135 assert_eq!(editor.content(), "hell");
136 }
137
138 #[test]
139 fn test_cursor_movement() {
140 let mut editor = Editor::with_content("hello".to_string());
141 editor.move_left();
142 assert_eq!(editor.cursor(), 4);
143 editor.move_home();
144 assert_eq!(editor.cursor(), 0);
145 editor.move_end();
146 assert_eq!(editor.cursor(), 5);
147 }
148}