titik/
area_buffer.rs

1use crossterm::event::{
2    KeyCode,
3    KeyEvent,
4};
5use unicode_width::UnicodeWidthChar;
6
7/// Area buffer is a 2 dimensional text buffer
8#[derive(Default, Debug, PartialEq, Clone)]
9pub(crate) struct AreaBuffer {
10    pub(crate) content: Vec<Vec<char>>,
11    content_width: usize,
12    cursor_loc_x: usize,
13    cursor_loc_y: usize,
14}
15
16impl AreaBuffer {
17    fn add_char(&mut self, c: char) {
18        let line = self
19            .content
20            .get_mut(self.cursor_loc_y)
21            .expect("must have a line");
22        line.insert(self.cursor_loc_x, c);
23        self.cursor_loc_x += 1;
24        self.calc_content_width();
25    }
26
27    fn calc_content_width(&mut self) {
28        self.content_width = self
29            .content
30            .iter()
31            .map(|line| line.len())
32            .max()
33            .unwrap_or(0);
34    }
35
36    pub(crate) fn add_line<S: ToString>(&mut self, s: S) {
37        let line = s.to_string().chars().collect();
38        self.content.push(line);
39        self.cursor_loc_y += 1;
40        self.calc_content_width();
41    }
42
43    pub fn process_key_event(
44        &mut self,
45        KeyEvent { code, modifiers: _ }: KeyEvent,
46    ) {
47        match code {
48            KeyCode::Char(c) => {
49                self.add_char(c);
50            }
51            KeyCode::Enter => {
52                if let Some(line) = self.content.get_mut(self.cursor_loc_y) {
53                    let new_line = line.split_off(self.cursor_loc_x);
54                    self.cursor_loc_y += 1;
55                    self.cursor_loc_x = 0;
56                    self.content.insert(self.cursor_loc_y, new_line);
57                    self.calc_content_width();
58                }
59            }
60            KeyCode::Left => {
61                if self.cursor_loc_x > 0 {
62                    self.cursor_loc_x -= 1;
63                }
64            }
65            KeyCode::Right => {
66                if let Some(line) = self.content.get(self.cursor_loc_y) {
67                    if self.cursor_loc_x < line.len() {
68                        self.cursor_loc_x += 1;
69                    }
70                }
71            }
72            KeyCode::Up => {
73                if self.cursor_loc_y > 0 {
74                    self.cursor_loc_y -= 1;
75                    if let Some(line) = self.content.get(self.cursor_loc_y) {
76                        if self.cursor_loc_x > line.len() {
77                            self.cursor_loc_x = line.len();
78                        }
79                    }
80                }
81            }
82            KeyCode::Down => {
83                if self.cursor_loc_y < self.content.len() - 1 {
84                    self.cursor_loc_y += 1;
85                    if let Some(line) = self.content.get(self.cursor_loc_y) {
86                        if self.cursor_loc_x > line.len() {
87                            self.cursor_loc_x = line.len();
88                        }
89                    }
90                }
91            }
92            KeyCode::Backspace => {
93                if let Some(line) = self.content.get_mut(self.cursor_loc_y) {
94                    if self.cursor_loc_x > 0 && line.len() > 0 {
95                        self.cursor_loc_x -= 1;
96                        line.remove(self.cursor_loc_x);
97                    }
98                    self.calc_content_width();
99                }
100            }
101            KeyCode::Delete => {
102                if let Some(line) = self.content.get_mut(self.cursor_loc_y) {
103                    if self.cursor_loc_x < line.len() {
104                        line.remove(self.cursor_loc_x);
105                    }
106                    self.calc_content_width();
107                }
108            }
109            _ => (),
110        }
111    }
112
113    pub fn set_cursor_loc(&mut self, cursor_x: usize, cursor_y: usize) {
114        self.cursor_loc_x = cursor_x;
115        self.cursor_loc_y = cursor_y;
116    }
117
118    pub fn get_cursor_location(&self) -> (usize, usize) {
119        (self.cursor_loc_x, self.cursor_loc_y)
120    }
121
122    pub fn height(&self) -> usize {
123        self.content.len()
124    }
125
126    pub fn width(&self) -> usize {
127        self.content_width
128    }
129}
130
131impl From<String> for AreaBuffer {
132    fn from(s: String) -> Self {
133        let mut content = vec![];
134        let mut cursor_loc_x = 0;
135        let mut cursor_loc_y = 0;
136        let mut content_width = 0;
137        for line in s.lines() {
138            cursor_loc_x = 0;
139            let mut row = vec![];
140            for ch in line.chars() {
141                row.push(ch);
142                cursor_loc_x += 1;
143                if let Some(width) = ch.width() {
144                    for _i in 1..width {
145                        row.push('\0');
146                        cursor_loc_x += 1;
147                    }
148                }
149            }
150            if content_width < row.len() {
151                content_width = row.len();
152            }
153            content.push(row);
154            cursor_loc_y += 1;
155        }
156        if cursor_loc_y > 0 {
157            cursor_loc_y = cursor_loc_y - 1;
158        }
159
160        AreaBuffer {
161            content,
162            content_width,
163            cursor_loc_x,
164            cursor_loc_y,
165        }
166    }
167}
168
169impl ToString for AreaBuffer {
170    fn to_string(&self) -> String {
171        let mut lines = vec![];
172        for row in self.content.iter() {
173            let row_contents: Vec<String> = row
174                .iter()
175                .filter(|ch| **ch != '\0')
176                .map(ToString::to_string)
177                .collect();
178            let line = row_contents.join("").trim_end().to_string();
179            lines.push(line);
180        }
181        lines.join("\n")
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crossterm::event::KeyCode;
189
190    #[test]
191    fn add_char1() {
192        let s = "The quick brown fox ".to_string();
193        let mut area_buffer = AreaBuffer::from(s);
194        assert_eq!(0, area_buffer.cursor_loc_y);
195        assert_eq!(20, area_buffer.cursor_loc_x);
196        area_buffer.add_char('j');
197        assert_eq!("The quick brown fox j", area_buffer.to_string());
198    }
199
200    #[test]
201    fn add_char2() {
202        let s = "The quick brown fox ".to_string();
203        let mut area_buffer = AreaBuffer::from(s);
204        assert_eq!(0, area_buffer.cursor_loc_y);
205        assert_eq!(20, area_buffer.cursor_loc_x);
206        area_buffer.add_char('j');
207        assert_eq!(21, area_buffer.cursor_loc_x);
208        area_buffer.add_char('u');
209        assert_eq!(22, area_buffer.cursor_loc_x);
210        assert_eq!("The quick brown fox ju", area_buffer.to_string());
211    }
212
213    #[test]
214    fn add_enter() {
215        let s = "The quick brown fox ".to_string();
216        let mut area_buffer = AreaBuffer::from(s);
217        assert_eq!(0, area_buffer.cursor_loc_y);
218        assert_eq!(20, area_buffer.cursor_loc_x);
219        area_buffer.process_key_event(KeyCode::Enter.into());
220        assert_eq!(1, area_buffer.cursor_loc_y);
221        assert_eq!(0, area_buffer.cursor_loc_x);
222        area_buffer.process_key_event(KeyCode::Char('j').into());
223        assert_eq!(1, area_buffer.cursor_loc_y);
224        assert_eq!(1, area_buffer.cursor_loc_x);
225    }
226}