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 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 { lock.flush() } else { Ok(()) }
45    }
46}
47
48#[async_trait(?Send)]
49impl Console for TrivialConsole {
50    fn clear(&mut self, _how: ClearType) -> io::Result<()> {
51        Ok(())
52    }
53
54    fn color(&self) -> (Option<u8>, Option<u8>) {
55        (None, None)
56    }
57
58    fn set_color(&mut self, _fg: Option<u8>, _bg: Option<u8>) -> io::Result<()> {
59        Ok(())
60    }
61
62    fn enter_alt(&mut self) -> io::Result<()> {
63        Ok(())
64    }
65
66    fn hide_cursor(&mut self) -> io::Result<()> {
67        Ok(())
68    }
69
70    fn is_interactive(&self) -> bool {
71        true
72    }
73
74    fn leave_alt(&mut self) -> io::Result<()> {
75        Ok(())
76    }
77
78    #[cfg_attr(not(debug_assertions), allow(unused))]
79    fn locate(&mut self, pos: CharsXY) -> io::Result<()> {
80        #[cfg(debug_assertions)]
81        {
82            let size = self.size_chars()?;
83            assert!(pos.x < size.x);
84            assert!(pos.y < size.y);
85        }
86        Ok(())
87    }
88
89    fn move_within_line(&mut self, _off: i16) -> io::Result<()> {
90        Ok(())
91    }
92
93    fn print(&mut self, text: &str) -> io::Result<()> {
94        let text = remove_control_chars(text);
95
96        let stdout = io::stdout();
97        let mut stdout = stdout.lock();
98        stdout.write_all(text.as_bytes())?;
99        stdout.write_all(b"\n")?;
100        Ok(())
101    }
102
103    async fn poll_key(&mut self) -> io::Result<Option<Key>> {
104        Ok(None)
105    }
106
107    async fn read_key(&mut self) -> io::Result<Key> {
108        read_key_from_stdin(&mut self.buffer)
109    }
110
111    fn show_cursor(&mut self) -> io::Result<()> {
112        Ok(())
113    }
114
115    fn size_chars(&self) -> io::Result<CharsXY> {
116        let lines = get_env_var_as_u16("LINES").unwrap_or(DEFAULT_LINES);
117        let columns = get_env_var_as_u16("COLUMNS").unwrap_or(DEFAULT_COLUMNS);
118        Ok(CharsXY::new(columns, lines))
119    }
120
121    fn write(&mut self, text: &str) -> io::Result<()> {
122        let text = remove_control_chars(text);
123
124        let stdout = io::stdout();
125        let mut stdout = stdout.lock();
126        stdout.write_all(text.as_bytes())?;
127        self.maybe_flush(stdout)
128    }
129
130    fn sync_now(&mut self) -> io::Result<()> {
131        if self.sync_enabled { Ok(()) } else { io::stdout().flush() }
132    }
133
134    fn set_sync(&mut self, enabled: bool) -> io::Result<bool> {
135        if !self.sync_enabled {
136            io::stdout().flush()?;
137        }
138        let previous = self.sync_enabled;
139        self.sync_enabled = enabled;
140        Ok(previous)
141    }
142}