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    get_env_var_as_u16, read_key_from_stdin, remove_control_chars, CharsXY, ClearType, Console, Key,
20};
21use async_trait::async_trait;
22use std::collections::VecDeque;
23use std::io::{self, StdoutLock, Write};
24
25/// Default number of columns for when `COLUMNS` is not set.
26const DEFAULT_COLUMNS: u16 = 80;
27
28/// Default number of lines for when `LINES` is not set.
29const DEFAULT_LINES: u16 = 24;
30
31/// Implementation of the EndBASIC console with minimal functionality.
32#[derive(Default)]
33pub struct TrivialConsole {
34    /// Line-oriented buffer to hold input when not operating in raw mode.
35    buffer: VecDeque<Key>,
36
37    /// Whether video syncing is enabled or not.
38    sync_enabled: bool,
39}
40
41impl TrivialConsole {
42    /// Flushes the console, which has already been written to via `lock`, if syncing is enabled.
43    fn maybe_flush(&self, mut lock: StdoutLock<'_>) -> io::Result<()> {
44        if self.sync_enabled {
45            lock.flush()
46        } else {
47            Ok(())
48        }
49    }
50}
51
52#[async_trait(?Send)]
53impl Console for TrivialConsole {
54    fn clear(&mut self, _how: ClearType) -> io::Result<()> {
55        Ok(())
56    }
57
58    fn color(&self) -> (Option<u8>, Option<u8>) {
59        (None, None)
60    }
61
62    fn set_color(&mut self, _fg: Option<u8>, _bg: Option<u8>) -> io::Result<()> {
63        Ok(())
64    }
65
66    fn enter_alt(&mut self) -> io::Result<()> {
67        Ok(())
68    }
69
70    fn hide_cursor(&mut self) -> io::Result<()> {
71        Ok(())
72    }
73
74    fn is_interactive(&self) -> bool {
75        true
76    }
77
78    fn leave_alt(&mut self) -> io::Result<()> {
79        Ok(())
80    }
81
82    #[cfg_attr(not(debug_assertions), allow(unused))]
83    fn locate(&mut self, pos: CharsXY) -> io::Result<()> {
84        #[cfg(debug_assertions)]
85        {
86            let size = self.size_chars()?;
87            assert!(pos.x < size.x);
88            assert!(pos.y < size.y);
89        }
90        Ok(())
91    }
92
93    fn move_within_line(&mut self, _off: i16) -> io::Result<()> {
94        Ok(())
95    }
96
97    fn print(&mut self, text: &str) -> io::Result<()> {
98        let text = remove_control_chars(text);
99
100        let stdout = io::stdout();
101        let mut stdout = stdout.lock();
102        stdout.write_all(text.as_bytes())?;
103        stdout.write_all(b"\n")?;
104        Ok(())
105    }
106
107    async fn poll_key(&mut self) -> io::Result<Option<Key>> {
108        Ok(None)
109    }
110
111    async fn read_key(&mut self) -> io::Result<Key> {
112        read_key_from_stdin(&mut self.buffer)
113    }
114
115    fn show_cursor(&mut self) -> io::Result<()> {
116        Ok(())
117    }
118
119    fn size_chars(&self) -> io::Result<CharsXY> {
120        let lines = get_env_var_as_u16("LINES").unwrap_or(DEFAULT_LINES);
121        let columns = get_env_var_as_u16("COLUMNS").unwrap_or(DEFAULT_COLUMNS);
122        Ok(CharsXY::new(columns, lines))
123    }
124
125    fn write(&mut self, text: &str) -> io::Result<()> {
126        let text = remove_control_chars(text);
127
128        let stdout = io::stdout();
129        let mut stdout = stdout.lock();
130        stdout.write_all(text.as_bytes())?;
131        self.maybe_flush(stdout)
132    }
133
134    fn sync_now(&mut self) -> io::Result<()> {
135        if self.sync_enabled {
136            Ok(())
137        } else {
138            io::stdout().flush()
139        }
140    }
141
142    fn set_sync(&mut self, enabled: bool) -> io::Result<bool> {
143        if !self.sync_enabled {
144            io::stdout().flush()?;
145        }
146        let previous = self.sync_enabled;
147        self.sync_enabled = enabled;
148        Ok(previous)
149    }
150}