1use crate::cell::SgrAttrs;
8
9const DEFAULT_TAB_INTERVAL: u16 = 8;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Cursor {
15 pub row: u16,
17 pub col: u16,
19 pub visible: bool,
21 pub pending_wrap: bool,
25 pub attrs: SgrAttrs,
27 scroll_top: u16,
29 scroll_bottom: u16,
31 tab_stops: Vec<bool>,
33 pub charset_slots: [u8; 4],
36 pub active_charset: u8,
38 pub single_shift: Option<u8>,
41}
42
43impl Cursor {
44 pub fn new(cols: u16, rows: u16) -> Self {
46 let mut tab_stops = vec![false; cols as usize];
47 for i in (0..cols).step_by(DEFAULT_TAB_INTERVAL as usize) {
48 tab_stops[i as usize] = true;
49 }
50 Self {
51 row: 0,
52 col: 0,
53 visible: true,
54 pending_wrap: false,
55 attrs: SgrAttrs::default(),
56 scroll_top: 0,
57 scroll_bottom: rows,
58 tab_stops,
59 charset_slots: [b'B'; 4],
60 active_charset: 0,
61 single_shift: None,
62 }
63 }
64
65 pub fn at(row: u16, col: u16) -> Self {
67 Self {
68 row,
69 col,
70 ..Self::default()
71 }
72 }
73
74 pub fn scroll_top(&self) -> u16 {
78 self.scroll_top
79 }
80
81 pub fn scroll_bottom(&self) -> u16 {
83 self.scroll_bottom
84 }
85
86 pub fn set_scroll_region(&mut self, top: u16, bottom: u16, rows: u16) {
91 if top < bottom && bottom <= rows {
92 self.scroll_top = top;
93 self.scroll_bottom = bottom;
94 }
95 }
96
97 pub fn reset_scroll_region(&mut self, rows: u16) {
99 self.scroll_top = 0;
100 self.scroll_bottom = rows;
101 }
102
103 pub fn in_scroll_region(&self) -> bool {
105 self.row >= self.scroll_top && self.row < self.scroll_bottom
106 }
107
108 pub fn clamp(&mut self, rows: u16, cols: u16) {
112 if rows > 0 {
113 self.row = self.row.min(rows - 1);
114 }
115 if cols > 0 {
116 self.col = self.col.min(cols - 1);
117 }
118 self.pending_wrap = false;
119 }
120
121 pub fn move_to(&mut self, row: u16, col: u16, rows: u16, cols: u16) {
124 self.row = row.min(rows.saturating_sub(1));
125 self.col = col.min(cols.saturating_sub(1));
126 self.pending_wrap = false;
127 }
128
129 pub fn move_up(&mut self, count: u16) {
131 let limit = self.scroll_top;
132 self.row = self.row.saturating_sub(count).max(limit);
133 self.pending_wrap = false;
134 }
135
136 pub fn move_down(&mut self, count: u16, rows: u16) {
138 let limit = self.scroll_bottom.min(rows).saturating_sub(1);
139 self.row = self.row.saturating_add(count).min(limit);
140 self.pending_wrap = false;
141 }
142
143 pub fn move_right(&mut self, count: u16, cols: u16) {
145 self.col = self.col.saturating_add(count).min(cols.saturating_sub(1));
146 self.pending_wrap = false;
147 }
148
149 pub fn move_left(&mut self, count: u16) {
151 self.col = self.col.saturating_sub(count);
152 self.pending_wrap = false;
153 }
154
155 pub fn carriage_return(&mut self) {
157 self.col = 0;
158 self.pending_wrap = false;
159 }
160
161 pub fn next_tab_stop(&self, cols: u16) -> u16 {
165 let start = (self.col as usize).saturating_add(1);
166 for i in start..self.tab_stops.len().min(cols as usize) {
167 if self.tab_stops[i] {
168 return i as u16;
169 }
170 }
171 cols.saturating_sub(1)
173 }
174
175 pub fn prev_tab_stop(&self) -> u16 {
177 if self.col == 0 {
178 return 0;
179 }
180 for i in (0..self.col as usize).rev() {
181 if self.tab_stops[i] {
182 return i as u16;
183 }
184 }
185 0
186 }
187
188 pub fn set_tab_stop(&mut self) {
190 if (self.col as usize) < self.tab_stops.len() {
191 self.tab_stops[self.col as usize] = true;
192 }
193 }
194
195 pub fn clear_tab_stop(&mut self) {
197 if (self.col as usize) < self.tab_stops.len() {
198 self.tab_stops[self.col as usize] = false;
199 }
200 }
201
202 pub fn clear_all_tab_stops(&mut self) {
204 for stop in &mut self.tab_stops {
205 *stop = false;
206 }
207 }
208
209 pub fn reset_tab_stops(&mut self, cols: u16) {
211 self.tab_stops = vec![false; cols as usize];
212 for i in (0..cols).step_by(DEFAULT_TAB_INTERVAL as usize) {
213 self.tab_stops[i as usize] = true;
214 }
215 }
216
217 pub fn resize(&mut self, new_cols: u16, new_rows: u16) {
221 let old_cols = self.tab_stops.len();
222 self.scroll_top = 0;
223 self.scroll_bottom = new_rows;
224 self.clamp(new_rows, new_cols);
225 self.tab_stops.resize(new_cols as usize, false);
227 for i in (0..new_cols).step_by(DEFAULT_TAB_INTERVAL as usize) {
229 let idx = i as usize;
230 if idx >= old_cols {
231 self.tab_stops[idx] = true;
232 }
233 }
234 }
235}
236
237impl Default for Cursor {
238 fn default() -> Self {
239 Self {
240 row: 0,
241 col: 0,
242 visible: true,
243 pending_wrap: false,
244 attrs: SgrAttrs::default(),
245 scroll_top: 0,
246 scroll_bottom: 24, tab_stops: {
248 let mut stops = vec![false; 80];
249 for i in (0..80).step_by(DEFAULT_TAB_INTERVAL as usize) {
250 stops[i] = true;
251 }
252 stops
253 },
254 charset_slots: [b'B'; 4],
255 active_charset: 0,
256 single_shift: None,
257 }
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Default)]
265pub struct SavedCursor {
266 pub row: u16,
267 pub col: u16,
268 pub attrs: SgrAttrs,
269 pub origin_mode: bool,
270 pub pending_wrap: bool,
271 pub charset_slots: [u8; 4],
272 pub active_charset: u8,
273}
274
275impl SavedCursor {
276 pub fn save(cursor: &Cursor, origin_mode: bool) -> Self {
278 Self {
279 row: cursor.row,
280 col: cursor.col,
281 attrs: cursor.attrs,
282 origin_mode,
283 pending_wrap: cursor.pending_wrap,
284 charset_slots: cursor.charset_slots,
285 active_charset: cursor.active_charset,
286 }
287 }
288
289 pub fn restore(&self, cursor: &mut Cursor) {
291 cursor.row = self.row;
292 cursor.col = self.col;
293 cursor.attrs = self.attrs;
294 cursor.pending_wrap = self.pending_wrap;
295 cursor.charset_slots = self.charset_slots;
296 cursor.active_charset = self.active_charset;
297 cursor.single_shift = None;
298 }
299}
300
301fn dec_graphics_char(ch: char) -> char {
308 match ch {
309 '`' => '\u{25C6}', 'a' => '\u{2592}', 'b' => '\u{2409}', 'c' => '\u{240C}', 'd' => '\u{240D}', 'e' => '\u{240A}', 'f' => '\u{00B0}', 'g' => '\u{00B1}', 'h' => '\u{2424}', 'i' => '\u{240B}', 'j' => '\u{2518}', 'k' => '\u{2510}', 'l' => '\u{250C}', 'm' => '\u{2514}', 'n' => '\u{253C}', 'o' => '\u{23BA}', 'p' => '\u{23BB}', 'q' => '\u{2500}', 'r' => '\u{23BC}', 's' => '\u{23BD}', 't' => '\u{251C}', 'u' => '\u{2524}', 'v' => '\u{2534}', 'w' => '\u{252C}', 'x' => '\u{2502}', 'y' => '\u{2264}', 'z' => '\u{2265}', '{' => '\u{03C0}', '|' => '\u{2260}', '}' => '\u{00A3}', '~' => '\u{00B7}', _ => ch,
341 }
342}
343
344pub fn translate_charset(ch: char, charset_designator: u8) -> char {
350 match charset_designator {
351 b'0' => dec_graphics_char(ch),
352 _ => ch,
353 }
354}
355
356impl Cursor {
357 pub fn effective_charset(&self) -> u8 {
360 if let Some(shift) = self.single_shift {
361 let slot = (shift as usize).min(3);
362 self.charset_slots[slot]
363 } else {
364 self.charset_slots[self.active_charset as usize & 3]
365 }
366 }
367
368 pub fn consume_single_shift(&mut self) {
370 self.single_shift = None;
371 }
372
373 pub fn designate_charset(&mut self, slot: u8, charset: u8) {
375 let idx = (slot as usize).min(3);
376 self.charset_slots[idx] = charset;
377 }
378
379 pub fn reset_charset(&mut self) {
381 self.charset_slots = [b'B'; 4];
382 self.active_charset = 0;
383 self.single_shift = None;
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390 use crate::cell::SgrFlags;
391
392 #[test]
393 fn default_cursor_at_origin() {
394 let c = Cursor::default();
395 assert_eq!(c.row, 0);
396 assert_eq!(c.col, 0);
397 assert!(c.visible);
398 assert!(!c.pending_wrap);
399 }
400
401 #[test]
402 fn cursor_new_with_dimensions() {
403 let c = Cursor::new(80, 24);
404 assert_eq!(c.scroll_top(), 0);
405 assert_eq!(c.scroll_bottom(), 24);
406 }
407
408 #[test]
409 fn cursor_at_position() {
410 let c = Cursor::at(5, 10);
411 assert_eq!(c.row, 5);
412 assert_eq!(c.col, 10);
413 }
414
415 #[test]
416 fn cursor_clamp_to_bounds() {
417 let mut c = Cursor::at(100, 200);
418 c.clamp(24, 80);
419 assert_eq!(c.row, 23);
420 assert_eq!(c.col, 79);
421 assert!(!c.pending_wrap);
422 }
423
424 #[test]
425 fn move_to_clamps() {
426 let mut c = Cursor::new(80, 24);
427 c.move_to(999, 999, 24, 80);
428 assert_eq!(c.row, 23);
429 assert_eq!(c.col, 79);
430 }
431
432 #[test]
433 fn move_up_stops_at_scroll_top() {
434 let mut c = Cursor::new(80, 24);
435 c.set_scroll_region(5, 20, 24);
436 c.row = 7;
437 c.move_up(10);
438 assert_eq!(c.row, 5);
439 }
440
441 #[test]
442 fn move_down_stops_at_scroll_bottom() {
443 let mut c = Cursor::new(80, 24);
444 c.set_scroll_region(0, 10, 24);
445 c.row = 8;
446 c.move_down(10, 24);
447 assert_eq!(c.row, 9); }
449
450 #[test]
451 fn move_left_stops_at_zero() {
452 let mut c = Cursor::new(80, 24);
453 c.col = 3;
454 c.move_left(100);
455 assert_eq!(c.col, 0);
456 }
457
458 #[test]
459 fn move_right_stops_at_margin() {
460 let mut c = Cursor::new(80, 24);
461 c.col = 70;
462 c.move_right(100, 80);
463 assert_eq!(c.col, 79);
464 }
465
466 #[test]
467 fn carriage_return() {
468 let mut c = Cursor::new(80, 24);
469 c.col = 42;
470 c.pending_wrap = true;
471 c.carriage_return();
472 assert_eq!(c.col, 0);
473 assert!(!c.pending_wrap);
474 }
475
476 #[test]
479 fn set_scroll_region() {
480 let mut c = Cursor::new(80, 24);
481 c.set_scroll_region(5, 20, 24);
482 assert_eq!(c.scroll_top(), 5);
483 assert_eq!(c.scroll_bottom(), 20);
484 }
485
486 #[test]
487 fn invalid_scroll_region_is_ignored() {
488 let mut c = Cursor::new(80, 24);
489 c.set_scroll_region(20, 5, 24); assert_eq!(c.scroll_top(), 0);
491 assert_eq!(c.scroll_bottom(), 24);
492 }
493
494 #[test]
495 fn reset_scroll_region() {
496 let mut c = Cursor::new(80, 24);
497 c.set_scroll_region(5, 20, 24);
498 c.reset_scroll_region(24);
499 assert_eq!(c.scroll_top(), 0);
500 assert_eq!(c.scroll_bottom(), 24);
501 }
502
503 #[test]
504 fn in_scroll_region() {
505 let mut c = Cursor::new(80, 24);
506 c.set_scroll_region(5, 15, 24);
507 c.row = 10;
508 assert!(c.in_scroll_region());
509 c.row = 3;
510 assert!(!c.in_scroll_region());
511 c.row = 15; assert!(!c.in_scroll_region());
513 }
514
515 #[test]
518 fn default_tab_stops_every_8() {
519 let c = Cursor::new(80, 24);
520 assert!(c.tab_stops[0]);
522 assert!(c.tab_stops[8]);
523 assert!(!c.tab_stops[7]);
524 assert!(c.tab_stops[16]);
525 }
526
527 #[test]
528 fn next_tab_stop() {
529 let c = Cursor::new(80, 24);
530 let mut c2 = c.clone();
532 c2.col = 0;
533 assert_eq!(c2.next_tab_stop(80), 8);
534
535 c2.col = 7;
537 assert_eq!(c2.next_tab_stop(80), 8);
538
539 c2.col = 8;
541 assert_eq!(c2.next_tab_stop(80), 16);
542 }
543
544 #[test]
545 fn prev_tab_stop() {
546 let c = Cursor::new(80, 24);
547 let mut c2 = c.clone();
548 c2.col = 10;
549 assert_eq!(c2.prev_tab_stop(), 8);
550
551 c2.col = 8;
552 assert_eq!(c2.prev_tab_stop(), 0);
553
554 c2.col = 0;
555 assert_eq!(c2.prev_tab_stop(), 0);
556 }
557
558 #[test]
559 fn set_and_clear_tab_stop() {
560 let mut c = Cursor::new(80, 24);
561 c.col = 5;
562 c.set_tab_stop();
563 assert!(c.tab_stops[5]);
564
565 c.col = 5;
566 c.clear_tab_stop();
567 assert!(!c.tab_stops[5]);
568 }
569
570 #[test]
571 fn clear_all_tab_stops() {
572 let mut c = Cursor::new(80, 24);
573 c.clear_all_tab_stops();
574 assert!(c.tab_stops.iter().all(|&s| !s));
575 }
576
577 #[test]
580 fn save_restore_roundtrip() {
581 let mut cursor = Cursor::at(5, 10);
582 cursor.attrs.flags = SgrFlags::BOLD;
583 cursor.pending_wrap = true;
584
585 let saved = SavedCursor::save(&cursor, true);
586 assert_eq!(saved.row, 5);
587 assert_eq!(saved.col, 10);
588 assert!(saved.origin_mode);
589
590 let mut new_cursor = Cursor::default();
591 saved.restore(&mut new_cursor);
592 assert_eq!(new_cursor.row, 5);
593 assert_eq!(new_cursor.col, 10);
594 assert!(new_cursor.pending_wrap);
595 assert_eq!(new_cursor.attrs.flags, SgrFlags::BOLD);
596 }
597
598 #[test]
601 fn move_to_zero_size_grid() {
602 let mut c = Cursor::default();
603 c.move_to(5, 5, 0, 0);
604 assert_eq!(c.row, 0);
606 assert_eq!(c.col, 0);
607 }
608
609 #[test]
610 fn tab_stop_past_end_returns_last_col() {
611 let mut c = Cursor::new(10, 1);
612 c.clear_all_tab_stops();
613 c.col = 5;
614 assert_eq!(c.next_tab_stop(10), 9);
615 }
616
617 #[test]
618 fn resize_wider_sets_tab_stops_on_new_columns() {
619 let mut c = Cursor::new(80, 24);
620 c.tab_stops[8] = false;
622
623 c.resize(120, 24);
624
625 assert!(c.tab_stops[0], "original tab stop at 0 must be preserved");
627 assert!(
628 !c.tab_stops[8],
629 "user-cleared tab stop at 8 must remain cleared"
630 );
631 assert!(c.tab_stops[80], "new column 80 must get a default tab stop");
633 assert!(c.tab_stops[88], "new column 88 must get a default tab stop");
634 assert!(c.tab_stops[96], "new column 96 must get a default tab stop");
635 assert!(!c.tab_stops[81], "new column 81 must not have a tab stop");
636 }
637
638 #[test]
639 fn resize_narrower_preserves_existing_tab_stops() {
640 let mut c = Cursor::new(80, 24);
641 c.resize(40, 24);
642
643 assert!(c.tab_stops[0]);
645 assert!(c.tab_stops[8]);
646 assert!(c.tab_stops[16]);
647 assert!(c.tab_stops[24]);
648 assert!(c.tab_stops[32]);
649 assert_eq!(c.tab_stops.len(), 40);
650 }
651
652 #[test]
655 fn default_charset_is_ascii() {
656 let c = Cursor::new(80, 24);
657 assert_eq!(c.charset_slots, [b'B'; 4]);
658 assert_eq!(c.active_charset, 0);
659 assert!(c.single_shift.is_none());
660 assert_eq!(c.effective_charset(), b'B');
661 }
662
663 #[test]
664 fn designate_charset_sets_slot() {
665 let mut c = Cursor::new(80, 24);
666 c.designate_charset(0, b'0'); assert_eq!(c.charset_slots[0], b'0');
668 assert_eq!(c.effective_charset(), b'0');
669 assert_eq!(c.charset_slots[1], b'B');
671 }
672
673 #[test]
674 fn single_shift_overrides_effective_charset() {
675 let mut c = Cursor::new(80, 24);
676 c.charset_slots[2] = b'0'; c.single_shift = Some(2);
678 assert_eq!(c.effective_charset(), b'0');
679 c.consume_single_shift();
680 assert!(c.single_shift.is_none());
681 assert_eq!(c.effective_charset(), b'B');
683 }
684
685 #[test]
686 fn reset_charset_restores_defaults() {
687 let mut c = Cursor::new(80, 24);
688 c.charset_slots = [b'0'; 4];
689 c.active_charset = 2;
690 c.single_shift = Some(3);
691 c.reset_charset();
692 assert_eq!(c.charset_slots, [b'B'; 4]);
693 assert_eq!(c.active_charset, 0);
694 assert!(c.single_shift.is_none());
695 }
696
697 #[test]
698 fn dec_graphics_translation() {
699 use super::translate_charset;
700 assert_eq!(translate_charset('j', b'0'), '┘');
702 assert_eq!(translate_charset('k', b'0'), '┐');
703 assert_eq!(translate_charset('l', b'0'), '┌');
704 assert_eq!(translate_charset('m', b'0'), '└');
705 assert_eq!(translate_charset('q', b'0'), '─');
706 assert_eq!(translate_charset('x', b'0'), '│');
707 assert_eq!(translate_charset('n', b'0'), '┼');
708 assert_eq!(translate_charset('t', b'0'), '├');
710 assert_eq!(translate_charset('u', b'0'), '┤');
711 assert_eq!(translate_charset('v', b'0'), '┴');
712 assert_eq!(translate_charset('w', b'0'), '┬');
713 assert_eq!(translate_charset('`', b'0'), '◆');
715 assert_eq!(translate_charset('a', b'0'), '▒');
716 assert_eq!(translate_charset('~', b'0'), '·');
717 assert_eq!(translate_charset('{', b'0'), 'π');
718 assert_eq!(translate_charset('A', b'0'), 'A');
720 assert_eq!(translate_charset('q', b'B'), 'q');
722 }
723
724 #[test]
725 fn save_restore_preserves_charset() {
726 let mut cursor = Cursor::new(80, 24);
727 cursor.designate_charset(0, b'0');
728 cursor.designate_charset(1, b'A');
729 cursor.active_charset = 1;
730
731 let saved = SavedCursor::save(&cursor, false);
732 assert_eq!(saved.charset_slots[0], b'0');
733 assert_eq!(saved.charset_slots[1], b'A');
734 assert_eq!(saved.active_charset, 1);
735
736 let mut new_cursor = Cursor::new(80, 24);
737 saved.restore(&mut new_cursor);
738 assert_eq!(new_cursor.charset_slots[0], b'0');
739 assert_eq!(new_cursor.charset_slots[1], b'A');
740 assert_eq!(new_cursor.active_charset, 1);
741 assert!(new_cursor.single_shift.is_none());
742 }
743}