Skip to main content

printer/printer/
cursor.rs

1use std::{cell::RefCell, rc::Rc};
2mod bound;
3pub use bound::Bound;
4mod raw;
5use raw::Raw;
6
7use crate::buffer::Buffer;
8/// input is shown with x in this example
9/// |In: x
10/// |    x
11/// |    x
12
13#[derive(Debug, Clone, Copy)]
14pub struct CursorPosition {
15    pub current_pos: (usize, usize),
16    pub starting_pos: (usize, usize),
17}
18
19#[derive(Debug, Clone)]
20pub struct Cursor<W: std::io::Write> {
21    //pub for tests only
22    #[cfg(test)]
23    pub(super) pos: CursorPosition,
24    #[cfg(test)]
25    pub(super) bound: Bound,
26
27    #[cfg(not(test))]
28    pos: CursorPosition,
29    #[cfg(not(test))]
30    bound: Bound,
31
32    pub prompt_len: usize,
33    pub raw: Raw<W>,
34
35    copy: CursorPosition,
36}
37
38impl<W: std::io::Write> Cursor<W> {
39    pub fn new(raw: Rc<RefCell<W>>, prompt_len: usize) -> Self {
40        let mut raw = Raw { raw };
41        let (width, height) = raw.size().unwrap_or((400, 400));
42        let current_pos = raw.get_current_pos().unwrap_or((0, 0));
43
44        let pos = CursorPosition {
45            current_pos,
46            starting_pos: (0, current_pos.1),
47        };
48        Self {
49            pos,
50            copy: pos,
51            bound: Bound::new(width, height),
52            raw,
53            prompt_len,
54        }
55    }
56
57    pub fn width(&self) -> usize {
58        self.bound.width
59    }
60
61    pub fn height(&self) -> usize {
62        self.bound.height
63    }
64
65    pub fn current_pos(&self) -> (usize, usize) {
66        self.pos.current_pos
67    }
68
69    pub fn set_current_pos(&mut self, xpos: usize, ypos: usize) {
70        self.pos.current_pos = (xpos, ypos);
71    }
72
73    pub fn starting_pos(&self) -> (usize, usize) {
74        self.pos.starting_pos
75    }
76
77    pub fn set_starting_pos(&mut self, xpos: usize, ypos: usize) {
78        self.pos.starting_pos = (xpos, ypos);
79    }
80
81    pub fn save_position(&mut self) {
82        self.copy = self.pos;
83        self.raw
84            .save_position()
85            .expect("failed to save cursor position");
86    }
87
88    pub fn move_right_unbounded(&mut self) {
89        self.move_right_inner(self.bound.width - 1);
90    }
91
92    pub fn move_right(&mut self) {
93        self.move_right_inner(self.current_row_bound());
94    }
95
96    pub fn move_right_inner_optimized(&mut self) {
97        // Performance: Make sure to not move the cursor if cursor_pos = last_cursor_pos+1 because it moves automatically
98        if self.pos.current_pos.0 == self.bound.width - 1 {
99            self.pos.current_pos.0 = self.prompt_len;
100            self.pos.current_pos.1 += 1;
101            self.goto_internal_pos();
102        } else {
103            self.pos.current_pos.0 += 1;
104        }
105    }
106    fn move_right_inner(&mut self, bound: usize) {
107        if self.pos.current_pos.0 == bound {
108            self.pos.current_pos.0 = self.prompt_len;
109            self.pos.current_pos.1 += 1;
110        } else {
111            self.pos.current_pos.0 += 1;
112        }
113        self.goto_internal_pos();
114    }
115
116    pub fn move_left(&mut self) {
117        if self.pos.current_pos.0 == self.prompt_len {
118            self.pos.current_pos.0 = self.previous_row_bound();
119            self.pos.current_pos.1 -= 1;
120        } else {
121            self.pos.current_pos.0 -= 1;
122        }
123        self.goto_internal_pos();
124    }
125
126    pub fn move_up_bounded(&mut self, count: u16) {
127        self.move_up(count);
128        self.pos.current_pos.0 = std::cmp::min(self.pos.current_pos.0, self.current_row_bound());
129        self.goto_internal_pos();
130    }
131
132    pub fn move_up(&mut self, count: u16) {
133        self.pos.current_pos.1 = self.pos.current_pos.1.saturating_sub(count as usize);
134        self.raw.move_up(count).expect("failed to move cursor up");
135    }
136
137    pub fn move_down_bounded(&mut self, count: u16, buffer: &Buffer) {
138        self.move_down(count);
139        // check if we're out of bound
140        self.pos.current_pos.0 = std::cmp::min(self.pos.current_pos.0, self.current_row_bound());
141        // check if we passed the buffer
142        let last_pos = self.input_last_pos(buffer);
143        if self.pos.current_pos.1 >= last_pos.1 && self.pos.current_pos.0 >= last_pos.0 {
144            self.pos.current_pos = last_pos;
145        }
146        self.goto_internal_pos();
147    }
148
149    pub fn move_down(&mut self, count: u16) {
150        self.pos.current_pos.1 += count as usize;
151        self.raw
152            .move_down(count)
153            .expect("failed to move cursor down");
154    }
155
156    pub fn use_current_row_as_starting_row(&mut self) {
157        self.pos.starting_pos.1 = self.pos.current_pos.1;
158    }
159
160    pub fn previous_row_bound(&self) -> usize {
161        self.bound.get_bound(self.pos.current_pos.1 - 1)
162    }
163
164    pub fn current_row_bound(&self) -> usize {
165        self.bound.get_bound(self.pos.current_pos.1)
166    }
167
168    pub fn reset_bound(&mut self) {
169        self.bound.reset();
170    }
171
172    pub fn bound_current_row_at_current_col(&mut self) {
173        self.bound
174            .set_bound(self.pos.current_pos.1, self.pos.current_pos.0);
175    }
176
177    /// Check if adding new_lines to the buffer will make it overflow the screen height and return
178    /// that amount if so (0 if not)
179    pub fn screen_height_overflow_by_new_lines(&self, buffer: &Buffer, new_lines: usize) -> usize {
180        // if current row  + new lines < self.raw..bound.height there is no overflow so unwrap to 0
181        (new_lines + self.input_last_pos(buffer).1).saturating_sub(self.bound.height - 1)
182    }
183
184    pub fn restore_position(&mut self) {
185        self.pos = self.copy;
186        self.raw
187            .restore_position()
188            .expect("failed to restore cursor position");
189    }
190
191    pub fn goto_internal_pos(&mut self) {
192        self.raw
193            .goto(self.pos.current_pos.0 as u16, self.pos.current_pos.1 as u16)
194            .expect("failed to move cursor");
195    }
196
197    pub fn goto(&mut self, x: usize, y: usize) {
198        self.pos.current_pos.0 = x;
199        self.pos.current_pos.1 = y;
200
201        self.goto_internal_pos();
202    }
203
204    pub fn hide(&mut self) {
205        self.raw.hide().expect("failed to hide cursor");
206    }
207
208    pub fn show(&mut self) {
209        self.raw.show().expect("failed to show cursor");
210    }
211
212    pub fn goto_start(&mut self) {
213        self.pos.current_pos.0 = self.pos.starting_pos.0;
214        self.pos.current_pos.1 = self.pos.starting_pos.1;
215        self.goto_internal_pos();
216    }
217
218    pub fn goto_input_start_col(&mut self) {
219        self.pos.current_pos.0 = self.pos.starting_pos.0 + self.prompt_len;
220        self.pos.current_pos.1 = self.pos.starting_pos.1;
221        self.goto_internal_pos();
222    }
223
224    pub fn is_at_last_terminal_col(&self) -> bool {
225        self.pos.current_pos.0 == self.bound.width - 1
226    }
227
228    pub fn is_at_last_terminal_row(&self) -> bool {
229        self.pos.current_pos.1 == self.bound.height - 1
230    }
231
232    pub fn is_at_line_end(&self) -> bool {
233        self.pos.current_pos.0 == self.current_row_bound()
234    }
235
236    pub fn is_at_line_start(&self) -> bool {
237        self.pos.current_pos.0 == self.prompt_len
238    }
239
240    pub fn is_at_col(&self, col: usize) -> bool {
241        self.pos.current_pos.0 == col
242    }
243
244    pub fn buffer_pos_to_cursor_pos(&self, buffer: &Buffer) -> (usize, usize) {
245        let last_buffer_pos = buffer.len();
246        let max_line_chars = self.bound.width - self.prompt_len;
247
248        let mut y = buffer
249            .iter()
250            .take(last_buffer_pos)
251            .filter(|c| **c == '\n')
252            .count();
253
254        let mut x = 0;
255        for i in 0..last_buffer_pos {
256            match buffer.get(i) {
257                Some('\n') => x = 0,
258                _ => x += 1,
259            };
260            if x == max_line_chars {
261                x = 0;
262                y += 1;
263            }
264        }
265
266        (x, y)
267    }
268
269    pub fn input_last_pos(&self, buffer: &Buffer) -> (usize, usize) {
270        let relative_pos = self.buffer_pos_to_cursor_pos(buffer);
271        //let relative_pos = buffer.last_buffer_pos_to_relative_cursor_pos(self.bound.width);
272        let x = relative_pos.0 + self.prompt_len;
273        let y = relative_pos.1 + self.pos.starting_pos.1;
274
275        (x, y)
276    }
277
278    pub fn move_to_input_last_row(&mut self, buffer: &Buffer) {
279        let input_last_row = self.input_last_pos(buffer).1;
280        self.goto(0, input_last_row);
281    }
282    pub fn goto_last_row(&mut self, buffer: &Buffer) {
283        self.pos.current_pos.1 = self.input_last_pos(buffer).1;
284        self.pos.current_pos.0 = std::cmp::min(self.pos.current_pos.0, self.current_row_bound());
285        self.goto_internal_pos();
286    }
287
288    pub fn is_at_first_input_line(&self) -> bool {
289        self.pos.current_pos.1 == self.pos.starting_pos.1
290    }
291
292    pub fn is_at_last_input_line(&self, buffer: &Buffer) -> bool {
293        self.pos.current_pos.1 == self.input_last_pos(buffer).1
294    }
295
296    pub fn cursor_pos_to_buffer_pos(&self) -> usize {
297        self.pos.current_pos.0 - self.prompt_len
298            + self.bound.bounds_sum(
299                self.pos.starting_pos.1,
300                self.pos.current_pos.1,
301                self.prompt_len,
302            )
303    }
304
305    pub fn goto_next_row_terminal_start(&mut self) {
306        self.goto(0, self.pos.current_pos.1 + 1);
307    }
308
309    pub fn update_dimensions(&mut self, width: u16, height: u16) {
310        self.bound = Bound::new(width as usize, height as usize);
311    }
312}