263 lines
8.7 KiB
Rust
263 lines
8.7 KiB
Rust
use alloc::{string::String, vec::Vec, boxed::Box, format};
|
|
use alloc::string::ToString;
|
|
use core::any::Any;
|
|
use crate::std::{application::{Application, Error}, render::Window};
|
|
|
|
use async_trait::async_trait;
|
|
use vga::writers::PrimitiveDrawing;
|
|
use crate::apps::calc::Calculator;
|
|
use crate::apps::editor::Editor;
|
|
use crate::apps::grapher::Grapher;
|
|
use crate::apps::tasks::Tasks;
|
|
use crate::games::gameoflife::GameOfLife;
|
|
use crate::games::paper_rs::GameBoard;
|
|
use crate::{games, println, serial_println, utils};
|
|
use crate::std::application::Error::ApplicationError;
|
|
use crate::std::io::{Color, ColorCode, Display, KeyStroke, Serial, Stdin};
|
|
use crate::std::render::{ColouredChar, Frame, Position, RenderError};
|
|
use crate::std::time::timer;
|
|
use crate::user::lib::libgui::cg_core::CgComponent;
|
|
use crate::utils::crystalfetch::CrystalFetch;
|
|
use crate::utils::gigachad_detector::GigachadDetector;
|
|
use crate::utils::rickroll::Rickroll;
|
|
|
|
pub struct ZxqSH {
|
|
history: Vec<String>,
|
|
history_idx: usize,
|
|
|
|
window: Window,
|
|
|
|
// the buffer is a vec of coloured characters
|
|
// we use a 1d vec so that the terminal can be resized.
|
|
buffer: Vec<ColouredChar>,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Application for ZxqSH {
|
|
fn new(window: Option<Window>) -> Result<ZxqSH, Error> {
|
|
match window {
|
|
Some(window) => {
|
|
Ok(ZxqSH {
|
|
history: Vec::new(),
|
|
history_idx: 0,
|
|
window,
|
|
buffer: Vec::new(),
|
|
})
|
|
}
|
|
None => Err(Error::NoWindow),
|
|
}
|
|
}
|
|
|
|
async fn run(&mut self, _args: Vec<String>) -> Result<(), Error> {
|
|
self.window.set_dimensions(78, 23);
|
|
self.window.set_position(1, 1);
|
|
self.window.set_title("Terminal");
|
|
self.window.open();
|
|
|
|
loop {
|
|
match self.next().await {
|
|
Err(e) => {
|
|
self.write(format!("Error: {:?}", e), Color::Yellow);
|
|
return Err(e);
|
|
},
|
|
Ok(exit) => {
|
|
if exit { return Ok(()) }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ZxqSH {
|
|
async fn next(&mut self) -> Result<bool, Error> {
|
|
|
|
// update cycle for the shell
|
|
|
|
// TODO: prompt
|
|
let mut command = self.input().await;
|
|
// TODO: exit if necessary
|
|
|
|
// TODO: execute command
|
|
if let Err(e) = self.execute(command, Vec::new()).await {
|
|
match e {
|
|
Error::UnknownCommand(e) => {
|
|
self.write("Unknown command\n".to_string(), Color::Yellow);
|
|
}
|
|
_ => {
|
|
self.write(format!("Error: {:?}\n", e), Color::Yellow);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
fn write(&mut self, string: String, color: Color) {
|
|
for ch in string.chars() {
|
|
self.write_char(ch, color);
|
|
}
|
|
}
|
|
|
|
fn write_char(&mut self, ch: char, color: Color) {
|
|
self.buffer.push(ColouredChar::coloured(ch, ColorCode::new(color, Color::Black)));
|
|
while self.buffer.len() > 100000 {
|
|
self.buffer.remove(0);
|
|
}
|
|
|
|
if let Ok(frame) = self.render() {
|
|
self.window.render(&frame).unwrap();
|
|
}
|
|
}
|
|
|
|
fn backspace(&mut self) {
|
|
let _ = self.buffer.pop();
|
|
if let Ok(frame) = self.render() {
|
|
self.window.render(&frame).unwrap();
|
|
}
|
|
}
|
|
|
|
async fn input(&mut self) -> String {
|
|
let mut string = String::new();
|
|
self.write("ZxqS> ".to_string(), Color::Cyan);
|
|
|
|
loop {
|
|
let ch = Stdin::keystroke().await;
|
|
match ch {
|
|
KeyStroke::Char(c) => {
|
|
match c {
|
|
'\x08' => {
|
|
if string.len() == 0 { continue; }
|
|
string.pop();
|
|
self.backspace()
|
|
},
|
|
'\n' => {
|
|
self.write_char(c, Color::White);
|
|
break
|
|
},
|
|
_ => {
|
|
self.write_char(c, Color::White);
|
|
string.push(c)
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
string
|
|
}
|
|
|
|
fn get_col(&self) -> usize {
|
|
let term_width = self.window.dimensions().x as isize;
|
|
let mut col = self.buffer.iter().rev().take_while(|c| c.character != '\n').count() as isize;
|
|
|
|
while col - term_width >= 0 {
|
|
col -= term_width;
|
|
}
|
|
|
|
col as usize
|
|
}
|
|
|
|
fn lines(&self) -> Vec<Vec<ColouredChar>> {
|
|
self.buffer
|
|
.split(|c| c.character == '\n')
|
|
.map(|line| line.to_vec())
|
|
.collect()
|
|
}
|
|
|
|
async fn execute(&self, cmd: String, args: Vec<String>) -> Result<(), Error> {
|
|
|
|
let window = Window::new();
|
|
|
|
match cmd.as_str() {
|
|
"calculate" | "calc" | "solve" => Calculator::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"games/connect4" => crate::user::bin::games::connect4::Game::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"rickroll" => Rickroll::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"crystalfetch" => CrystalFetch::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"tasks" => Tasks::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"VGA" => {
|
|
use vga::colors::Color16;
|
|
use vga::writers::{GraphicsWriter, Graphics640x480x16};
|
|
let mode = Graphics640x480x16::new();
|
|
mode.set_mode();
|
|
mode.clear_screen(Color16::Black);
|
|
mode.draw_line((80, 60), (120, 420), Color16::Cyan);
|
|
},
|
|
"graph" => Grapher::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"games/snake" => games::snake::Game::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"games/asteroids" => games::asteroids::Game::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"games/pong" => games::pong::Game::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"games/paper.rs" => games::paper_rs::GameBoard::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"serial" => println!("{}", Serial::reply_char('e')),
|
|
"games/gameoflife" => GameOfLife::new(Some(window)).expect("couldn't open window").run(Vec::new()).await?,
|
|
"games/tetris" => {
|
|
// games::tetris::TetrisEngine::new().expect("couldn't open window").run(Vec::new()).await?;
|
|
}
|
|
"gigachad?" => utils::gigachad_detector::GigachadDetector::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
"editor" => Editor::new(Some(window)).expect("couldn't open window").run(args).await?,
|
|
// direct OS functions (not applications)
|
|
"echo" => {
|
|
println!(
|
|
"Crystal: '{}'",
|
|
args.into_iter()
|
|
.map(|mut s| {
|
|
s.push_str(" ");
|
|
s
|
|
})
|
|
.collect::<String>()
|
|
)
|
|
}
|
|
"clear" => Display::clear(),
|
|
"time" => timer(),
|
|
_ => return Err(Error::UnknownCommand(cmd))
|
|
};
|
|
|
|
if let Ok(frame) = self.render() {
|
|
self.window.render(&frame).unwrap();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl CgComponent for ZxqSH {
|
|
fn render(&self) -> Result<Frame, RenderError> {
|
|
let mut frame = Frame::from_window(&self.window);
|
|
|
|
let term_height = self.window.dimensions().y;
|
|
let term_width = self.window.dimensions().x;
|
|
|
|
self.window.move_cursor(self.get_col() as i32, term_height as i32 - 1)?;
|
|
|
|
// render the contents of the terminal to a frame
|
|
|
|
let mut line = term_height - 1;
|
|
let mut col = self.get_col();
|
|
let buff = self.lines();
|
|
let mut lines = buff.iter();
|
|
|
|
for line_chars in lines.rev() {
|
|
col = line_chars.len() % term_width;
|
|
|
|
for c in line_chars.iter().rev() {
|
|
if c.character == '\n' { continue; }
|
|
frame.write(Position::new(col, line), c.clone())?;
|
|
|
|
if col <= 0 {
|
|
line -= 1;
|
|
col = term_width;
|
|
}
|
|
|
|
col -= 1;
|
|
}
|
|
|
|
line -= 1;
|
|
}
|
|
|
|
Ok(frame)
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn Any {
|
|
self as &dyn Any
|
|
}
|
|
} |