diff --git a/Cargo.lock b/Cargo.lock index aa77f7c..2b775d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -20,6 +31,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "crossterm" version = "0.25.0" @@ -45,13 +67,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "ctrlc" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "lambda" version = "0.1.0" dependencies = [ + "colored", "crossterm", + "ctrlc", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.137" @@ -89,6 +138,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "parking_lot" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 23443e5..1a9392f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,5 @@ edition = "2021" [dependencies] crossterm = "0.25" +colored = "2" +ctrlc = "3.2.3" diff --git a/src/editor.rs b/src/editor.rs index e82877d..bc2f5d7 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,7 +1,11 @@ -enum Mode { - Normal, - Insert, - Select +pub struct Config<'a> { + pub logo: &'a str, +} + +impl<'a> Config<'a> { + pub fn new() -> Self { + Self { logo: "λ" } + } } pub struct Buffer<'a> { @@ -10,21 +14,41 @@ pub struct Buffer<'a> { pub path: &'a str, } +pub enum Mode { + Normal, + Insert, + Select, + Command, +} + +impl Mode { + pub fn as_str(&self) -> &str { + match self { + Mode::Normal => "NORMAL", + Mode::Insert => "INSERT", + Mode::Select => "SELECT", + Mode::Command => "COMMAND", + } + } +} + pub struct Editor<'a> { + pub config: Config<'a>, pub buffer: Buffer<'a>, - pub cursor: [i32; 2], - mode: Mode, + pub cursors: Vec, + pub mode: Mode, } impl<'a> Editor<'a> { pub fn new() -> Self { Editor { + config: Config::new(), buffer: Buffer { data: Vec::from([String::from("Hello"), String::from("World")]), name: "[No Name]", path: "/home/spy", }, - cursor: [0, 0], + cursors: Vec::from([0]), mode: Mode::Normal, } } diff --git a/src/main.rs b/src/main.rs index b1db43d..6e25b09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,9 @@ -use std::time::Duration; - -mod terminal; mod editor; +mod terminal; +mod tui; fn main() { let lambda = editor::Editor::new(); - let mut term = terminal::Terminal::new().unwrap(); - loop { - for line in lambda.buffer.data { - terminal::Terminal::write(format!("{line}")); - mut term.cursor.move_to(0, 1); - }; - std::thread::sleep(Duration::from_secs(3)); - break; - }; - terminal::Terminal::exit(); + let mut screen = terminal::Screen::new().unwrap(); + tui::start(&mut screen, lambda); } diff --git a/src/terminal.rs b/src/terminal.rs index 81e5045..d1f7dc3 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,76 +1,124 @@ +use crossterm::cursor::{Hide, MoveTo, Show}; +use crossterm::style::Print; use crossterm::terminal; use crossterm::{execute, ErrorKind}; -use crossterm::style::Print; -use crossterm::cursor::{CursorShape, MoveTo}; -use std::io::{stdout, Write}; - -pub struct Size { - pub width: usize, - pub height: usize, -} +use std::io::stdout; +// Struct for holding coordinates +#[derive(Copy, Clone)] pub struct Coords { pub x: usize, pub y: usize, } +// Creating a coordinates from two values impl Coords { pub fn from(x: usize, y: usize) -> Self { - Self { - x: 0, - y: 0, - } + Self { x, y } } } +// A cursor for writing to the terminal screen +#[derive(Copy, Clone)] pub struct Cursor { pub position: Coords, - pub shape: CursorShape, + pub hidden: bool, } +// When a cursor is created impl Cursor { pub fn new() -> Result { - let cursor = Self { + let mut cursor = Self { position: Coords::from(0, 0), - shape: CursorShape::Block, + hidden: true, }; - Cursor::move_to(&mut cursor, 0, 0); + Cursor::move_to(&mut cursor, Coords::from(0, 0)); + Cursor::hide(&mut cursor); Ok(cursor) } - - pub fn move_to(&mut self, x: u16, y: u16) { - self.position = Coords::from(x as usize, y as usize); - execute!(stdout(), MoveTo(x, y)).unwrap() - } } -pub struct Terminal { +// Cursor methods +impl Cursor { + pub fn move_to(&mut self, position: Coords) { + // Set the new position of the cursor + self.position = position; + + // Move the cursor to the desired posiition in the terminal + execute!(stdout(), MoveTo(position.x as u16, position.y as u16)).unwrap(); + } + + pub fn hide(&mut self) { + // Remember that the cursor is hidden + self.hidden = true; + + // Hide the cursor from the terminal screen + execute!(stdout(), Hide).unwrap(); + } + + pub fn show(&mut self) { + // Remember that the cursor isn't hidden + self.hidden = false; + + // Show the cursor to the terminal screen + execute!(stdout(), Show).unwrap(); + } +} + +// A struct for holding the size of the terminal +pub struct Size { + pub width: usize, + pub height: usize, +} + +// The terminal screen +pub struct Screen { pub size: Size, pub cursor: Cursor, } -impl Terminal { +// For when a new terminal screen is created +impl Screen { pub fn new() -> Result { + // Get the size of the terminal let size = terminal::size()?; - Terminal::clear(); - Terminal::enter(); - Ok(Self { + + // Define a new terminal screen struct + let mut screen = Self { size: Size { width: size.0 as usize, height: size.1 as usize, }, cursor: Cursor::new().unwrap(), - }) - } + }; - pub fn enter() { - // Enter the current terminal + // Empty the terminal screen + Screen::clear(); + + // Enter the terminal screen + screen.enter(); + + // Return a result containing the terminal + Ok(screen) + } +} + +// Terminal functions and methods for managing the terminal +impl Screen { + pub fn enter(&mut self) { + // Hide the cursor + self.cursor.hide(); + + // Enter the terminal screen terminal::enable_raw_mode().unwrap(); execute!(stdout(), terminal::EnterAlternateScreen).unwrap(); } - pub fn exit() { - // Exit the current terminal + pub fn exit(&mut self) { + // Show the cursor + self.cursor.show(); + + // Exit the terminal screen execute!(stdout(), terminal::LeaveAlternateScreen).unwrap(); terminal::disable_raw_mode().unwrap(); } @@ -84,4 +132,10 @@ impl Terminal { // Clears the terminal screen execute!(stdout(), terminal::Clear(terminal::ClearType::All)).unwrap(); } + + pub fn write_at(&mut self, text: String, position: Coords) { + // Writes a line at a set of coordinates + self.cursor.move_to(position); + Screen::write(text); + } } diff --git a/src/tui.rs b/src/tui.rs new file mode 100644 index 0000000..79ee749 --- /dev/null +++ b/src/tui.rs @@ -0,0 +1,56 @@ +use crate::editor::Editor; +use crate::terminal::{Coords, Screen}; +use colored::Colorize; +use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers}; + +pub fn draw_status(screen: &mut Screen, editor: &Editor) { + // Calculate where to draw the status bar + let status_height = screen.size.height - 2; + + // Get the editor logo from the config + let editor_logo = &format!(" {} ", editor.config.logo) as &str; + // Write the editor logo + screen.write_at( + editor_logo.bright_yellow().bold().reversed().to_string(), + Coords::from(0, status_height), + ); + + // Get the current mode into a string + let mode_string = &format!(" {} ", editor.mode.as_str()) as &str; + // Calculate where to write the current mode + let x = editor_logo.len() - 1; + // Write the current mode + screen.write_at( + mode_string.green().bold().reversed().to_string(), + Coords::from(x, status_height), + ); + + // Get the current open file name + let file_name = &format!(" {} ", editor.buffer.name) as &str; + // Calculate where to write the file name + let x = editor_logo.len() + mode_string.len() - 1; + // Write the current file name + screen.write_at( + file_name.magenta().bold().reversed().to_string(), + Coords::from(x, status_height), + ); +} + +pub fn start(screen: &mut Screen, editor: Editor) { + loop { + // Draw the status bar + draw_status(screen, &editor); + + // Check for any key presses + match read().unwrap() { + Event::Key(KeyEvent { + code: KeyCode::Char('q'), + modifiers: KeyModifiers::CONTROL, + .. + }) => break, + _ => (), + } + } + + screen.exit(); +}