1#![cfg_attr(not(test), no_std)]
17
18#[cfg(test)]
19use std as core;
20
21#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Copy, Clone)]
23pub struct Row(pub u8);
24
25#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Copy, Clone)]
27pub struct Col(pub u8);
28
29#[derive(Debug, Copy, Clone)]
31pub struct Position {
32 pub row: Row,
34 pub col: Col,
36}
37
38#[derive(Debug, Copy, Clone)]
39pub enum EscapeCharMode {
40 Waiting,
41 Seen,
42}
43
44#[derive(Debug, Copy, Clone)]
46pub enum ControlCharMode {
47 Interpret,
48 Display,
49}
50
51#[derive(Debug, Copy, Clone)]
52pub enum SpecialChar {
54 Linefeed,
55 CarriageReturn,
56 Tab,
57 Backspace,
58 Delete,
59 Escape,
60}
61
62pub trait BaseConsole {
67 type Error;
68
69 fn get_width(&self) -> Col;
71
72 fn get_height(&self) -> Row;
74
75 fn set_col(&mut self, col: Col) -> Result<(), Self::Error>;
77
78 fn set_row(&mut self, row: Row) -> Result<(), Self::Error>;
80
81 fn set_pos(&mut self, pos: Position) -> Result<(), Self::Error>;
83
84 fn set_pos_unbounded(&mut self, pos: Position) {
87 let _ = self.set_pos(pos);
88 }
89
90 fn get_pos(&self) -> Position;
92
93 fn set_control_char_mode(&mut self, mode: ControlCharMode);
95
96 fn get_control_char_mode(&self) -> ControlCharMode;
98
99 fn set_escape_char_mode(&mut self, mode: EscapeCharMode);
101
102 fn get_escape_char_mode(&self) -> EscapeCharMode;
104
105 fn scroll_screen(&mut self) -> Result<(), Self::Error>;
107
108 fn move_cursor_right(&mut self) -> Result<(), Self::Error> {
112 let mut pos = self.get_pos();
113 if pos.col < self.get_width() {
114 pos.col.incr();
116 self.set_pos_unbounded(pos);
118 } else {
119 pos.col = Col::origin();
121 if pos.row == self.get_height() {
122 self.set_pos_unbounded(pos);
124 self.scroll_screen()?;
125 } else {
126 pos.row.incr();
128 self.set_pos_unbounded(pos);
129 }
130 }
131 Ok(())
132 }
133}
134
135pub trait AsciiConsole: BaseConsole {
139 fn write_char_at(&mut self, ch: u8, pos: Position) -> Result<(), Self::Error>;
142
143 fn handle_escape(&mut self, escaped_char: u8) -> bool;
147
148 fn write_string(&mut self, s: &[u8]) -> Result<(), Self::Error> {
152 for ch in s.iter() {
153 self.write_character(*ch)?;
154 }
155 Ok(())
156 }
157
158 fn write_character(&mut self, ch: u8) -> Result<(), Self::Error> {
160 match self.get_escape_char_mode() {
161 EscapeCharMode::Seen => {
162 if self.handle_escape(ch) {
163 self.set_escape_char_mode(EscapeCharMode::Waiting);
164 }
165 }
166 EscapeCharMode::Waiting => {
167 let mut pos = self.get_pos();
168 match self.is_special(ch) {
169 Some(SpecialChar::Linefeed) => {
171 pos.col = Col::origin();
172 if pos.row == self.get_height() {
173 self.set_pos_unbounded(pos);
174 self.scroll_screen()?;
175 } else {
176 pos.row.incr();
177 self.set_pos_unbounded(pos);
178 }
179 }
180 Some(SpecialChar::CarriageReturn) => {
182 pos.col = Col::origin();
183 self.set_pos_unbounded(pos);
184 }
185 Some(SpecialChar::Tab) => {
187 let tabs = pos.col.0 / 9;
188 pos.col.0 = (tabs + 1) * 9;
189 pos.col.bound(self.get_width());
190 self.set_pos_unbounded(pos);
191 }
192 Some(SpecialChar::Backspace) => {
194 if pos.col > Col::origin() {
195 pos.col.decr();
196 self.set_pos_unbounded(pos);
197 }
198 }
199 Some(SpecialChar::Delete) => {}
201 Some(SpecialChar::Escape) => {
203 self.set_escape_char_mode(EscapeCharMode::Seen);
204 }
205 None => {
206 self.write_char_at(ch, pos)?;
207 self.move_cursor_right()?;
208 }
209 }
210 }
211 }
212 Ok(())
213 }
214
215 fn write_string_at(&mut self, s: &[u8], pos: Position) -> Result<(), Self::Error> {
219 self.set_pos(pos)?;
220 self.write_string(s)?;
221 Ok(())
222 }
223
224 fn is_special(&self, ch: u8) -> Option<SpecialChar> {
226 match self.get_control_char_mode() {
227 ControlCharMode::Interpret => match ch {
228 b'\n' => Some(SpecialChar::Linefeed),
229 b'\r' => Some(SpecialChar::CarriageReturn),
230 b'\t' => Some(SpecialChar::Tab),
231 0x1b => Some(SpecialChar::Escape),
232 0x7f => Some(SpecialChar::Delete),
233 0x08 => Some(SpecialChar::Backspace),
234 _ => None,
235 },
236 _ => None,
237 }
238 }
239}
240
241pub trait UnicodeConsole: BaseConsole {
244 fn write_char_at(&mut self, ch: char, pos: Position) -> Result<(), Self::Error>;
247
248 fn handle_escape(&mut self, escaped_char: char) -> bool;
252
253 fn write_string(&mut self, s: &str) -> Result<(), Self::Error> {
257 for ch in s.chars() {
258 self.write_character(ch)?;
259 }
260 Ok(())
261 }
262
263 fn write_character(&mut self, ch: char) -> Result<(), Self::Error> {
265 match self.get_escape_char_mode() {
266 EscapeCharMode::Seen => {
267 if self.handle_escape(ch) {
268 self.set_escape_char_mode(EscapeCharMode::Waiting);
269 }
270 }
271 EscapeCharMode::Waiting => {
272 let mut pos = self.get_pos();
273 match self.is_special(ch) {
274 Some(SpecialChar::Linefeed) => {
276 pos.col = Col::origin();
277 if pos.row == self.get_height() {
278 self.set_pos_unbounded(pos);
279 self.scroll_screen()?;
280 } else {
281 pos.row.incr();
282 self.set_pos_unbounded(pos);
283 }
284 }
285 Some(SpecialChar::CarriageReturn) => {
287 pos.col = Col::origin();
288 self.set_pos_unbounded(pos);
289 }
290 Some(SpecialChar::Tab) => {
292 let tabs = pos.col.0 / 9;
293 pos.col.0 = (tabs + 1) * 9;
294 pos.col.bound(self.get_width());
295 self.set_pos_unbounded(pos);
296 }
297 Some(SpecialChar::Backspace) => {
299 if pos.col > Col::origin() {
300 pos.col.decr();
301 self.set_pos_unbounded(pos);
302 }
303 }
304 Some(SpecialChar::Delete) => {}
306 Some(SpecialChar::Escape) => {
308 self.set_escape_char_mode(EscapeCharMode::Seen);
309 }
310 None => {
311 self.write_char_at(ch, pos)?;
312 self.move_cursor_right()?;
313 }
314 }
315 }
316 }
317 Ok(())
318 }
319
320 fn write_string_at(&mut self, s: &str, pos: Position) -> Result<(), Self::Error> {
324 self.set_pos(pos)?;
325 self.write_string(s)?;
326 Ok(())
327 }
328
329 fn is_special(&self, ch: char) -> Option<SpecialChar> {
331 match self.get_control_char_mode() {
332 ControlCharMode::Interpret => match ch {
333 '\n' => Some(SpecialChar::Linefeed),
334 '\r' => Some(SpecialChar::CarriageReturn),
335 '\t' => Some(SpecialChar::Tab),
336 '\u{001b}' => Some(SpecialChar::Escape),
337 '\u{007f}' => Some(SpecialChar::Delete),
338 '\u{0008}' => Some(SpecialChar::Backspace),
339 _ => None,
340 },
341 _ => None,
342 }
343 }
344}
345
346impl Position {
347 pub fn new(row: Row, col: Col) -> Position {
349 Position { row, col }
350 }
351
352 pub fn origin() -> Position {
354 Position {
355 row: Row::origin(),
356 col: Col::origin(),
357 }
358 }
359}
360
361impl Col {
362 pub fn origin() -> Col {
364 Col(0)
365 }
366
367 pub fn incr(&mut self) -> &mut Self {
368 self.0 += 1;
369 self
370 }
371
372 pub fn decr(&mut self) -> &mut Self {
373 self.0 -= 1;
374 self
375 }
376
377 pub fn bound(&mut self, other: Col) -> &mut Self {
378 if self.0 > other.0 {
379 self.0 = other.0;
380 }
381 self
382 }
383}
384
385impl Row {
386 pub fn origin() -> Row {
388 Row(0)
389 }
390
391 pub fn incr(&mut self) -> &mut Self {
392 self.0 += 1;
393 self
394 }
395
396 pub fn decr(&mut self) -> &mut Self {
397 self.0 -= 1;
398 self
399 }
400
401 pub fn bound(&mut self, other: Row) -> &mut Self {
402 if self.0 > other.0 {
403 self.0 = other.0;
404 }
405 self
406 }
407}
408
409impl core::convert::From<u8> for Row {
410 fn from(num: u8) -> Row {
411 Row(num)
412 }
413}
414
415impl core::convert::From<usize> for Row {
416 fn from(num: usize) -> Row {
417 Row(num as u8)
418 }
419}
420
421impl core::convert::From<u8> for Col {
422 fn from(num: u8) -> Col {
423 Col(num)
424 }
425}
426
427impl core::convert::From<usize> for Col {
428 fn from(num: usize) -> Col {
429 Col(num as u8)
430 }
431}
432
433#[cfg(test)]
434mod test {
435 use super::*;
436 use core::fmt::Write;
437
438 const WIDTH: u8 = 80;
439 const HEIGHT: u8 = 25;
440
441 #[derive(Copy, Clone)]
442 struct Line {
443 chars: [char; WIDTH as usize],
444 }
445
446 struct TestConsole {
447 pos: Position,
448 lines: [Line; HEIGHT as usize],
449 mode: ControlCharMode,
450 }
451
452 impl TestConsole {
453 fn new() -> TestConsole {
454 let line = Line {
455 chars: [' '; WIDTH as usize],
456 };
457 TestConsole {
458 lines: [line; HEIGHT as usize],
459 pos: Position::origin(),
460 mode: ControlCharMode::Interpret,
461 }
462 }
463 }
464
465 impl Console for TestConsole {
466 type Error = ();
467
468 fn get_width(&self) -> Col {
470 Col(WIDTH - 1)
471 }
472
473 fn get_height(&self) -> Row {
475 Row(HEIGHT - 1)
476 }
477
478 fn set_col(&mut self, col: Col) -> Result<(), Self::Error> {
480 self.pos.col = col;
481 Ok(())
482 }
483
484 fn set_row(&mut self, row: Row) -> Result<(), Self::Error> {
486 self.pos.row = row;
487 Ok(())
488 }
489
490 fn set_pos(&mut self, pos: Position) -> Result<(), Self::Error> {
492 self.pos = pos;
493 Ok(())
494 }
495
496 fn get_pos(&self) -> Position {
498 self.pos
499 }
500
501 fn set_control_char_mode(&mut self, mode: ControlCharMode) {
503 self.mode = mode;
504 }
505
506 fn get_control_char_mode(&self) -> ControlCharMode {
508 self.mode
509 }
510
511 fn scroll_screen(&mut self) -> Result<(), Self::Error> {
513 for row in 0..HEIGHT - 1 {
514 self.lines[row as usize] = self.lines[(row as usize) + 1];
515 self.lines[(HEIGHT as usize) - 1] = Line {
516 chars: [' '; WIDTH as usize],
517 };
518 }
519 Ok(())
520 }
521
522 fn write_char_at(&mut self, ch: char, pos: Position) -> Result<(), Self::Error> {
525 self.lines[pos.row.0 as usize].chars[pos.col.0 as usize] = ch;
526 Ok(())
527 }
528 }
529
530 impl core::fmt::Write for TestConsole {
531 fn write_str(&mut self, s: &str) -> core::fmt::Result {
532 self.write_string(s).unwrap();
533 Ok(())
534 }
535 }
536
537 #[test]
538 fn test_write() {
539 let mut c = TestConsole::new();
540 c.write_str("Hello").unwrap();
541 assert_eq!(
542 &c.lines[0].chars[0..10],
543 &"Hello ".chars().collect::<Vec<char>>()[..]
544 );
545 assert_eq!(c.pos.row, Row::origin());
546 assert_eq!(c.pos.col, Col(5));
547 }
548
549 #[test]
550 fn test_lf() {
551 let mut c = TestConsole::new();
552 c.write_str("Hello\n").unwrap();
553 assert_eq!(
554 &c.lines[0].chars[0..10],
555 &"Hello ".chars().collect::<Vec<char>>()[..]
556 );
557 assert_eq!(c.pos.row, Row(1));
558 assert_eq!(c.pos.col, Col(0));
559 }
560
561 #[test]
562 fn test_cr() {
563 let mut c = TestConsole::new();
564 c.write_str("Hello\r123").unwrap();
565 assert_eq!(
566 &c.lines[0].chars[0..10],
567 &"123lo ".chars().collect::<Vec<char>>()[..]
568 );
569 assert_eq!(c.pos.row, Row(0));
570 assert_eq!(c.pos.col, Col(3));
571 }
572
573 #[test]
574 fn test_bs() {
575 let mut c = TestConsole::new();
576 c.write_str("Hello~\u{0008}!").unwrap();
577 assert_eq!(
578 &c.lines[0].chars[0..10],
579 &"Hello! ".chars().collect::<Vec<char>>()[..]
580 );
581 assert_eq!(c.pos.row, Row(0));
582 assert_eq!(c.pos.col, Col(6));
583 }
584
585 #[test]
586 fn test_tab() {
587 let mut c = TestConsole::new();
588 c.write_str("1\t2\tHello\t4").unwrap();
589 assert_eq!(
590 &c.lines[0].chars[0..28],
591 &"1 2 Hello 4"
592 .chars()
593 .collect::<Vec<char>>()[..]
594 );
595 assert_eq!(c.pos.row, Row(0));
596 assert_eq!(c.pos.col, Col(28));
597 }
598
599 #[test]
600 fn test_wrap() {
601 let mut c = TestConsole::new();
602 for line in 0..HEIGHT {
603 writeln!(c, "{}", line).unwrap();
604 }
605 assert_eq!(
607 &c.lines[0].chars[0..4],
608 &"1 ".chars().collect::<Vec<char>>()[..]
609 );
610 assert_eq!(c.pos.row, Row(HEIGHT - 1));
611 assert_eq!(c.pos.col, Col(0));
612 }
613
614}
615
616