Skip to main content

endbasic_std/console/
trivial.rs

1// EndBASIC
2// Copyright 2021 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License.  You may obtain a copy
6// of the License at:
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! Trivial stdio-based console implementation for when we have nothing else.
17
18use crate::console::{
19    CharsXY, ClearType, Console, Key, get_env_var_as_u16, read_key_from_stdin, remove_control_chars,
20};
21use crate::sound::BEEP_TONE;
22use async_trait::async_trait;
23use std::collections::VecDeque;
24use std::io::{self, StdoutLock, Write};
25use std::thread;
26
27/// Default number of columns for when `COLUMNS` is not set.
28const DEFAULT_COLUMNS: u16 = 80;
29
30/// Default number of lines for when `LINES` is not set.
31const DEFAULT_LINES: u16 = 24;
32
33/// Implementation of the EndBASIC console with minimal functionality.
34#[derive(Default)]
35pub struct TrivialConsole {
36    /// Line-oriented buffer to hold input when not operating in raw mode.
37    buffer: VecDeque<Key>,
38
39    /// Whether video syncing is enabled or not.
40    sync_enabled: bool,
41}
42
43impl TrivialConsole {
44    /// Flushes the console, which has already been written to via `lock`, if syncing is enabled.
45    fn maybe_flush(&self, mut lock: StdoutLock<'_>) -> io::Result<()> {
46        if self.sync_enabled { lock.flush() } else { Ok(()) }
47    }
48}
49
50#[async_trait(?Send)]
51impl Console for TrivialConsole {
52    fn clear(&mut self, _how: ClearType) -> io::Result<()> {
53        Ok(())
54    }
55
56    fn color(&self) -> (Option<u8>, Option<u8>) {
57        (None, None)
58    }
59
60    fn set_color(&mut self, _fg: Option<u8>, _bg: Option<u8>) -> io::Result<()> {
61        Ok(())
62    }
63
64    fn enter_alt(&mut self) -> io::Result<()> {
65        Ok(())
66    }
67
68    fn hide_cursor(&mut self) -> io::Result<()> {
69        Ok(())
70    }
71
72    fn is_interactive(&self) -> bool {
73        true
74    }
75
76    fn leave_alt(&mut self) -> io::Result<()> {
77        Ok(())
78    }
79
80    #[cfg_attr(not(debug_assertions), allow(unused))]
81    fn locate(&mut self, pos: CharsXY) -> io::Result<()> {
82        #[cfg(debug_assertions)]
83        {
84            let size = self.size_chars()?;
85            assert!(pos.x < size.x);
86            assert!(pos.y < size.y);
87        }
88        Ok(())
89    }
90
91    fn move_within_line(&mut self, _off: i16) -> io::Result<()> {
92        Ok(())
93    }
94
95    fn print(&mut self, text: &str) -> io::Result<()> {
96        let text = remove_control_chars(text);
97
98        let stdout = io::stdout();
99        let mut stdout = stdout.lock();
100        stdout.write_all(text.as_bytes())?;
101        stdout.write_all(b"\n")?;
102        Ok(())
103    }
104
105    async fn poll_key(&mut self) -> io::Result<Option<Key>> {
106        Ok(None)
107    }
108
109    async fn read_key(&mut self) -> io::Result<Key> {
110        read_key_from_stdin(&mut self.buffer)
111    }
112
113    fn show_cursor(&mut self) -> io::Result<()> {
114        Ok(())
115    }
116
117    fn size_chars(&self) -> io::Result<CharsXY> {
118        let lines = get_env_var_as_u16("LINES").unwrap_or(DEFAULT_LINES);
119        let columns = get_env_var_as_u16("COLUMNS").unwrap_or(DEFAULT_COLUMNS);
120        Ok(CharsXY::new(columns, lines))
121    }
122
123    fn write(&mut self, text: &str) -> io::Result<()> {
124        let text = remove_control_chars(text);
125
126        let stdout = io::stdout();
127        let mut stdout = stdout.lock();
128        stdout.write_all(text.as_bytes())?;
129        self.maybe_flush(stdout)
130    }
131
132    fn sync_now(&mut self) -> io::Result<()> {
133        if self.sync_enabled { Ok(()) } else { io::stdout().flush() }
134    }
135
136    fn set_sync(&mut self, enabled: bool) -> io::Result<bool> {
137        if !self.sync_enabled {
138            io::stdout().flush()?;
139        }
140        let previous = self.sync_enabled;
141        self.sync_enabled = enabled;
142        Ok(previous)
143    }
144
145    async fn beep(&mut self) -> io::Result<()> {
146        let stdout = io::stdout();
147        let mut stdout = stdout.lock();
148        stdout.write_all(b"\x07")?;
149        stdout.flush()?;
150        thread::sleep(BEEP_TONE.duration);
151        Ok(())
152    }
153}