printer/printer/
cursor.rs1use std::{cell::RefCell, rc::Rc};
2mod bound;
3pub use bound::Bound;
4mod raw;
5use raw::Raw;
6
7use crate::buffer::Buffer;
8#[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 #[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 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 self.pos.current_pos.0 = std::cmp::min(self.pos.current_pos.0, self.current_row_bound());
141 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 pub fn screen_height_overflow_by_new_lines(&self, buffer: &Buffer, new_lines: usize) -> usize {
180 (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 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}