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, 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, } #[async_trait] impl Application for ZxqSH { fn new(window: Option) -> Result { 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) -> 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 { // 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> { self.buffer .split(|c| c.character == '\n') .map(|line| line.to_vec()) .collect() } async fn execute(&self, cmd: String, args: Vec) -> 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::() ) } "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 { 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 } }