From ce9c03a32a1767969e289b99788c69d3e18a1464 Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 20:11:27 +0800 Subject: [PATCH 01/15] refactor: pound is good --- src/editor.rs | 160 ----------------------------------------- src/editor/terminal.rs | 90 ----------------------- src/main.rs | 42 +++++++++-- 3 files changed, 38 insertions(+), 254 deletions(-) delete mode 100644 src/editor.rs delete mode 100644 src/editor/terminal.rs diff --git a/src/editor.rs b/src/editor.rs deleted file mode 100644 index 772ae75..0000000 --- a/src/editor.rs +++ /dev/null @@ -1,160 +0,0 @@ -use core::cmp::min; -use crossterm::event::{ - read, - Event::{self, Key}, - KeyCode, KeyEvent, KeyEventKind, KeyModifiers, -}; -use std::io::Error; -mod terminal; -use terminal::{Position, Size, Terminal}; - -const NAME: &str = env!("CARGO_PKG_NAME"); -const VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[derive(Copy, Clone, Default)] -struct Location { - x: usize, - y: usize, -} - -#[derive(Default)] -pub struct Editor { - should_quit: bool, - location: Location, -} - -impl Editor { - pub fn run(&mut self) { - Terminal::initialize().unwrap(); - let result = self.repl(); - Terminal::terminate().unwrap(); - result.unwrap(); - } - - fn repl(&mut self) -> Result<(), Error> { - loop { - self.refresh_screen()?; - if self.should_quit { - break; - } - let event = read()?; - self.evaluate_event(&event)?; - } - Ok(()) - } - fn move_point(&mut self, key_code: KeyCode) -> Result<(), Error> { - let Location { mut x, mut y } = self.location; - let Size { height, width } = Terminal::size()?; - match key_code { - KeyCode::Up => { - y = y.saturating_sub(1); - } - KeyCode::Down => { - y = min(height.saturating_sub(1), y.saturating_add(1)); - } - KeyCode::Left => { - x = x.saturating_sub(1); - } - KeyCode::Right => { - x = min(width.saturating_sub(1), x.saturating_add(1)); - } - KeyCode::PageUp => { - y = 0; - } - KeyCode::PageDown => { - y = height.saturating_sub(1); - } - KeyCode::Home => { - x = 0; - } - KeyCode::End => { - x = width.saturating_sub(1); - } - _ => (), - } - self.location = Location { x, y }; - Ok(()) - } - fn evaluate_event(&mut self, event: &Event) -> Result<(), Error> { - if let Key(KeyEvent { - code, - modifiers, - kind: KeyEventKind::Press, - .. - }) = event - { - match code { - KeyCode::Char('q') if *modifiers == KeyModifiers::CONTROL => { - self.should_quit = true; - } - KeyCode::Up - | KeyCode::Down - | KeyCode::Left - | KeyCode::Right - | KeyCode::PageDown - | KeyCode::PageUp - | KeyCode::End - | KeyCode::Home => { - self.move_point(*code)?; - } - _ => (), - } - } - Ok(()) - } - fn refresh_screen(&self) -> Result<(), Error> { - Terminal::hide_caret()?; - Terminal::move_caret_to(Position::default())?; - if self.should_quit { - Terminal::clear_screen()?; - Terminal::print("Goodbye.\r\n")?; - } else { - Self::draw_rows()?; - Terminal::move_caret_to(Position { - col: self.location.x, - row: self.location.y, - })?; - } - - Terminal::show_caret()?; - Terminal::execute()?; - Ok(()) - } - fn draw_welcome_message() -> Result<(), Error> { - let mut welcome_message = format!("{NAME} editor -- version {VERSION}"); - let width = Terminal::size()?.width; - let len = welcome_message.len(); - // we allow this since we don't care if our welcome message is put _exactly_ in the middle. - // it's allowed to be a bit to the left or right. - #[allow(clippy::integer_division)] - let padding = (width.saturating_sub(len)) / 2; - - let spaces = " ".repeat(padding.saturating_sub(1)); - welcome_message = format!("~{spaces}{welcome_message}"); - welcome_message.truncate(width); - Terminal::print(welcome_message)?; - Ok(()) - } - fn draw_empty_row() -> Result<(), Error> { - Terminal::print("~")?; - Ok(()) - } - fn draw_rows() -> Result<(), Error> { - let Size { height, .. } = Terminal::size()?; - for current_row in 0..height { - Terminal::clear_line()?; - // we allow this since we don't care if our welcome message is put _exactly_ in the middle. - // it's allowed to be a bit up or down - #[allow(clippy::integer_division)] - if current_row == height / 3 { - Self::draw_welcome_message()?; - } else { - Self::draw_empty_row()?; - } - if current_row.saturating_add(1) < height { - Terminal::print("\r\n")?; - } - } - Ok(()) - } -} \ No newline at end of file diff --git a/src/editor/terminal.rs b/src/editor/terminal.rs deleted file mode 100644 index 1fb5d26..0000000 --- a/src/editor/terminal.rs +++ /dev/null @@ -1,90 +0,0 @@ -use core::fmt::Display; -use crossterm::cursor::{Hide, MoveTo, Show}; -use crossterm::style::Print; -use crossterm::terminal::{disable_raw_mode, enable_raw_mode, size, Clear, ClearType}; -use crossterm::{queue, Command}; -use std::io::{stdout, Error, Write}; - -#[derive(Copy, Clone)] -pub struct Size { - pub height: usize, - pub width: usize, -} -#[derive(Copy, Clone, Default)] -pub struct Position { - pub col: usize, - pub row: usize, -} -/// Represents the Terminal. -/// Edge Case for platforms where `usize` < `u16`: -/// Regardless of the actual size of the Terminal, this representation -/// only spans over at most `usize::MAX` or `u16::size` rows/columns, whichever is smaller. -/// Each size returned truncates to min(`usize::MAX`, `u16::MAX`) -/// And should you attempt to set the caret out of these bounds, it will also be truncated. -pub struct Terminal; - -impl Terminal { - pub fn terminate() -> Result<(), Error> { - Self::execute()?; - disable_raw_mode()?; - Ok(()) - } - pub fn initialize() -> Result<(), Error> { - enable_raw_mode()?; - Self::clear_screen()?; - Self::execute()?; - Ok(()) - } - pub fn clear_screen() -> Result<(), Error> { - Self::queue_command(Clear(ClearType::All))?; - Ok(()) - } - pub fn clear_line() -> Result<(), Error> { - Self::queue_command(Clear(ClearType::CurrentLine))?; - Ok(()) - } - /// Moves the caret to the given Position. - /// # Arguments - /// * `Position` - the `Position`to move the caret to. Will be truncated to `u16::MAX` if bigger. - pub fn move_caret_to(position: Position) -> Result<(), Error> { - // clippy::as_conversions: See doc above - #[allow(clippy::as_conversions, clippy::cast_possible_truncation)] - Self::queue_command(MoveTo(position.col as u16, position.row as u16))?; - Ok(()) - } - pub fn hide_caret() -> Result<(), Error> { - Self::queue_command(Hide)?; - Ok(()) - } - pub fn show_caret() -> Result<(), Error> { - Self::queue_command(Show)?; - Ok(()) - } - pub fn print(string: T) -> Result<(), Error> { - Self::queue_command(Print(string))?; - Ok(()) - } - - /// Returns the current size of this Terminal. - /// Edge Case for systems with `usize` < `u16`: - /// * A `Size` representing the terminal size. Any coordinate `z` truncated to `usize` if `usize` < `z` < `u16` - pub fn size() -> Result { - let (width_u16, height_u16) = size()?; - // clippy::as_conversions: See doc above - #[allow(clippy::as_conversions)] - let height = height_u16 as usize; - // clippy::as_conversions: See doc above - #[allow(clippy::as_conversions)] - let width = width_u16 as usize; - Ok(Size { height, width }) - } - pub fn execute() -> Result<(), Error> { - stdout().flush()?; - Ok(()) - } - - fn queue_command(command: T) -> Result<(), Error> { - queue!(stdout(), command)?; - Ok(()) - } -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b7d81fb..20bba67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,41 @@ -#![warn(clippy::all, clippy::pedantic)] -mod editor; -use editor::Editor; +use crossterm::event::{Event, KeyCode, KeyEvent}; /* modify */ +use crossterm::{event, terminal}; +use std::io; +use std::io::Read; +struct CleanUp; + +impl Drop for CleanUp { + fn drop(&mut self) { + terminal::disable_raw_mode().expect("Unable to disable raw mode") + } +} fn main() { - Editor::default().run(); + let _clean_up = CleanUp; + terminal::enable_raw_mode().expect("Could not turn on Raw mode"); + /* add the following */ + loop { + if let Event::Key(event) = event::read().expect("Failed to read line") { + match event { + KeyEvent { + code: KeyCode::Char('q'), + modifiers: event::KeyModifiers::NONE, .. + } => break, + _ => { + //todo + } + } + println!("{:?}\r", event); + }; + } + /* end */ + let mut buf = [0; 1]; + while io::stdin().read(&mut buf).expect("Failed to read") == 1 && buf != [b'q'] { + let character = buf[0] as char; + if character.is_control() { + println!("{}\r", character as u8) + } else { + println!("{}\r", character) + } + } } \ No newline at end of file From 07e54fdce742b7ac8cc794efccd321d2b2be915c Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 20:18:58 +0800 Subject: [PATCH 02/15] feat: different from the tutorial? --- src/main.rs | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index 20bba67..b185a55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ -use crossterm::event::{Event, KeyCode, KeyEvent}; /* modify */ +use crossterm::event::{Event, KeyCode, KeyEvent}; use crossterm::{event, terminal}; -use std::io; -use std::io::Read; +use std::time::Duration; /* add this line */ +use std::result::Result; struct CleanUp; impl Drop for CleanUp { @@ -10,32 +10,28 @@ impl Drop for CleanUp { } } -fn main() { +fn main() -> Result<(), Box> { let _clean_up = CleanUp; - terminal::enable_raw_mode().expect("Could not turn on Raw mode"); - /* add the following */ + terminal::enable_raw_mode()?; loop { - if let Event::Key(event) = event::read().expect("Failed to read line") { - match event { - KeyEvent { - code: KeyCode::Char('q'), - modifiers: event::KeyModifiers::NONE, .. - } => break, - _ => { - //todo + if event::poll(Duration::from_millis(1000))? { + if let Event::Key(event) = event::read()? { + match event { + KeyEvent { + code: KeyCode::Char('q'), + modifiers: event::KeyModifiers::CONTROL, + .. + } => break, + _ => { + //todo + } } - } - println!("{:?}\r", event); - }; - } - /* end */ - let mut buf = [0; 1]; - while io::stdin().read(&mut buf).expect("Failed to read") == 1 && buf != [b'q'] { - let character = buf[0] as char; - if character.is_control() { - println!("{}\r", character as u8) + println!("{:?}\r", event); + }; } else { - println!("{}\r", character) + //lL + println!("No input yet\r"); } } + Ok(()) } \ No newline at end of file From 14c6c4ab31ffd33d2ed990329ce1003379db87ea Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 20:41:22 +0800 Subject: [PATCH 03/15] feat: mass in main.rs --- src/main.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index b185a55..99f22e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use crossterm::event::{Event, KeyCode, KeyEvent}; use crossterm::{event, terminal}; -use std::time::Duration; /* add this line */ +use std::time::Duration; use std::result::Result; struct CleanUp; @@ -10,6 +10,30 @@ impl Drop for CleanUp { } } +struct Reader; + +impl Reader { + fn read_key(&self) -> Result> { + loop { + if event::poll(Duration::from_millis(500))? { + if let Event::Key(event) = event::read()? { + return Ok(event); + } + } + } + } +} + +struct Editor { + reader: Reader, +} + +impl Editor { + fn new() -> Self { + Self { reader: Reader } + } +} + fn main() -> Result<(), Box> { let _clean_up = CleanUp; terminal::enable_raw_mode()?; From 1bad0b96392a05e0d0d74f9aba7552323e9f65b3 Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 20:50:56 +0800 Subject: [PATCH 04/15] chore: crossterm 0.28.1->0.21.0 --- Cargo.lock | 173 +++++++++++++--------------------------------------- Cargo.toml | 2 +- src/main.rs | 44 ++++++++----- 3 files changed, 73 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f50dc7..814ca24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,9 +10,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" -version = "2.6.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cfg-if" @@ -22,15 +22,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crossterm" -version = "0.28.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "486d44227f71a1ef39554c0dc47e44b9f4139927c75043312690c3f476d1d788" dependencies = [ "bitflags", "crossterm_winapi", + "libc", "mio", "parking_lot", - "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -38,21 +38,20 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" dependencies = [ "winapi", ] [[package]] -name = "errno" -version = "0.3.10" +name = "instant" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ - "libc", - "windows-sys 0.59.0", + "cfg-if", ] [[package]] @@ -61,12 +60,6 @@ version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - [[package]] name = "lock_api" version = "0.4.12" @@ -85,61 +78,69 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "mio" -version = "1.0.3" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", - "wasi", - "windows-sys 0.52.0", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", ] [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ + "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if", + "instant", "libc", "redox_syscall", "smallvec", - "windows-targets", + "winapi", ] [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] -[[package]] -name = "rustix" -version = "0.38.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - [[package]] name = "rustpad" version = "0.1.0" @@ -189,12 +190,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "winapi" version = "0.3.9" @@ -216,85 +211,3 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index d558459..4245648 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -crossterm = "0.28.1" +crossterm = "0.21.0" diff --git a/src/main.rs b/src/main.rs index 99f22e6..ee8ae01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use crossterm::event::{Event, KeyCode, KeyEvent}; use crossterm::{event, terminal}; use std::time::Duration; -use std::result::Result; + struct CleanUp; impl Drop for CleanUp { @@ -11,9 +11,34 @@ impl Drop for CleanUp { } struct Reader; +struct Editor { + reader: Reader, +} + +impl Editor { + fn new() -> Self { + Self { reader: Reader } + } + + fn process_keypress(&self) -> crossterm::Result { + match self.reader.read_key()? { + KeyEvent { + code: KeyCode::Char('q'), + modifiers: event::KeyModifiers::CONTROL, + } => return Ok(false), + _ => {} + } + Ok(true) + } + + fn run(&self) -> crossterm::Result { + self.process_keypress() + } +} impl Reader { - fn read_key(&self) -> Result> { + + fn read_key(&self) -> crossterm::Result { loop { if event::poll(Duration::from_millis(500))? { if let Event::Key(event) = event::read()? { @@ -24,17 +49,7 @@ impl Reader { } } -struct Editor { - reader: Reader, -} - -impl Editor { - fn new() -> Self { - Self { reader: Reader } - } -} - -fn main() -> Result<(), Box> { +fn main() -> crossterm::Result<()> { let _clean_up = CleanUp; terminal::enable_raw_mode()?; loop { @@ -43,8 +58,7 @@ fn main() -> Result<(), Box> { match event { KeyEvent { code: KeyCode::Char('q'), - modifiers: event::KeyModifiers::CONTROL, - .. + modifiers: event::KeyModifiers::CONTROL, /* modify */ } => break, _ => { //todo From f56f91eb1bd5e58a4a2c52ffce27beb09b60d8a4 Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 21:01:46 +0800 Subject: [PATCH 05/15] feat: I am praying --- src/main.rs | 120 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index ee8ae01..76a544a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,69 @@ -use crossterm::event::{Event, KeyCode, KeyEvent}; -use crossterm::{event, terminal}; +use crossterm::event::*; +use crossterm::terminal::ClearType; +use crossterm::{cursor, event, execute, queue, terminal}; +use std::io; +use std::io::{stdout, Write}; use std::time::Duration; - struct CleanUp; impl Drop for CleanUp { fn drop(&mut self) { - terminal::disable_raw_mode().expect("Unable to disable raw mode") + terminal::disable_raw_mode().expect("Unable to disable raw mode"); + Output::clear_screen().expect("Error"); + } +} + +struct Output { + win_size: (usize, usize), + editor_contents: EditorContents, +} + +impl Output { + fn new() -> Self { + let win_size = terminal::size() + .map(|(x, y)| (x as usize, y as usize)) + .unwrap(); + Self { + win_size, + editor_contents: EditorContents::new(), + } + } + + fn clear_screen() -> crossterm::Result<()> { + execute!(stdout(), terminal::Clear(ClearType::All))?; + execute!(stdout(), cursor::MoveTo(0, 0)) + } + + fn draw_rows(&mut self) { + let screen_rows = self.win_size.1; + for i in 0..screen_rows { + self.editor_contents.push('~'); + if i < screen_rows - 1 { + self.editor_contents.push_str("\r\n"); + } + } + } + + fn refresh_screen(&mut self) -> crossterm::Result<()> { + queue!(self.editor_contents, terminal::Clear(ClearType::All), cursor::MoveTo(0, 0))?; + self.draw_rows(); + queue!(self.editor_contents, cursor::MoveTo(0, 0))?; + self.editor_contents.flush() } } struct Reader; struct Editor { reader: Reader, + output: Output, } impl Editor { fn new() -> Self { - Self { reader: Reader } + Self { + reader: Reader, + output: Output::new(), + } } fn process_keypress(&self) -> crossterm::Result { @@ -31,7 +77,8 @@ impl Editor { Ok(true) } - fn run(&self) -> crossterm::Result { + fn run(&mut self) -> crossterm::Result { + self.output.refresh_screen()?; self.process_keypress() } } @@ -49,27 +96,50 @@ impl Reader { } } +struct EditorContents { + content: String, +} + +impl EditorContents { + + fn new() -> Self { + Self { + content: String::new(), + } + } + + fn push(&mut self, ch: char) { + self.content.push(ch) + } + + fn push_str(&mut self, string: &str) { + self.content.push_str(string) + } +} + +impl io::Write for EditorContents { + fn write(&mut self, buf: &[u8]) -> io::Result { + match std::str::from_utf8(buf) { + Ok(s) => { + self.content.push_str(s); + Ok(s.len()) + } + Err(_) => Err(io::ErrorKind::WriteZero.into()), + } + } + + fn flush(&mut self) -> io::Result<()> { + let out = write!(stdout(), "{}", self.content); + stdout().flush()?; + self.content.clear(); + out + } +} + fn main() -> crossterm::Result<()> { let _clean_up = CleanUp; terminal::enable_raw_mode()?; - loop { - if event::poll(Duration::from_millis(1000))? { - if let Event::Key(event) = event::read()? { - match event { - KeyEvent { - code: KeyCode::Char('q'), - modifiers: event::KeyModifiers::CONTROL, /* modify */ - } => break, - _ => { - //todo - } - } - println!("{:?}\r", event); - }; - } else { - //lL - println!("No input yet\r"); - } - } + let mut editor = Editor::new(); + while editor.run()? {} Ok(()) } \ No newline at end of file From 723a5a0445bcb065b26575a8bad45c7146aaa234 Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 21:05:26 +0800 Subject: [PATCH 06/15] fix: Hide The Cursor When Repainting --- src/main.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 76a544a..a2cedb2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,11 +18,12 @@ struct Output { editor_contents: EditorContents, } + impl Output { fn new() -> Self { let win_size = terminal::size() .map(|(x, y)| (x as usize, y as usize)) - .unwrap(); + .unwrap(); Self { win_size, editor_contents: EditorContents::new(), @@ -38,6 +39,13 @@ impl Output { let screen_rows = self.win_size.1; for i in 0..screen_rows { self.editor_contents.push('~'); + //add the following + queue!( + self.editor_contents, + terminal::Clear(ClearType::UntilNewLine) + ) + .unwrap(); + //end if i < screen_rows - 1 { self.editor_contents.push_str("\r\n"); } @@ -45,9 +53,10 @@ impl Output { } fn refresh_screen(&mut self) -> crossterm::Result<()> { - queue!(self.editor_contents, terminal::Clear(ClearType::All), cursor::MoveTo(0, 0))?; + //modify + queue!(self.editor_contents, cursor::Hide, cursor::MoveTo(0, 0))?; self.draw_rows(); - queue!(self.editor_contents, cursor::MoveTo(0, 0))?; + queue!(self.editor_contents, cursor::MoveTo(0, 0), cursor::Show)?; self.editor_contents.flush() } } From 1604aaf02644efd0c631da613b663b49d2cb538a Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 21:11:45 +0800 Subject: [PATCH 07/15] feat: Welcome Message --- src/main.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index a2cedb2..58b591a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use crossterm::{cursor, event, execute, queue, terminal}; use std::io; use std::io::{stdout, Write}; use std::time::Duration; +use std::env; struct CleanUp; impl Drop for CleanUp { @@ -37,9 +38,25 @@ impl Output { fn draw_rows(&mut self) { let screen_rows = self.win_size.1; + let screen_columns = self.win_size.0; for i in 0..screen_rows { - self.editor_contents.push('~'); - //add the following + if i == screen_rows / 3 { + let name = env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "Unknown".to_string()); + let version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "Unknown".to_string()); + let mut welcome = format!("{} --- Version {}", name, version); + if welcome.len() > screen_columns { + welcome.truncate(screen_columns) + } + let mut padding = (screen_columns - welcome.len()) / 2; + if padding != 0 { + self.editor_contents.push('~'); + padding -= 1 + } + (0..padding).for_each(|_| self.editor_contents.push(' ')); + self.editor_contents.push_str(&welcome); + } else { + self.editor_contents.push('~'); + } queue!( self.editor_contents, terminal::Clear(ClearType::UntilNewLine) From e94332bbcc910ad246996fba98e4a640d6a1eb9b Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 21:18:00 +0800 Subject: [PATCH 08/15] feat: Move The Cursor --- src/main.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 58b591a..d250a5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ impl Drop for CleanUp { struct Output { win_size: (usize, usize), editor_contents: EditorContents, + cursor_controller: CursorController, } @@ -28,6 +29,7 @@ impl Output { Self { win_size, editor_contents: EditorContents::new(), + cursor_controller: CursorController::new(), } } @@ -36,6 +38,10 @@ impl Output { execute!(stdout(), cursor::MoveTo(0, 0)) } + fn move_cursor(&mut self, direction: KeyCode) { + self.cursor_controller.move_cursor(direction); + } + fn draw_rows(&mut self) { let screen_rows = self.win_size.1; let screen_columns = self.win_size.0; @@ -62,7 +68,6 @@ impl Output { terminal::Clear(ClearType::UntilNewLine) ) .unwrap(); - //end if i < screen_rows - 1 { self.editor_contents.push_str("\r\n"); } @@ -70,10 +75,15 @@ impl Output { } fn refresh_screen(&mut self) -> crossterm::Result<()> { - //modify queue!(self.editor_contents, cursor::Hide, cursor::MoveTo(0, 0))?; self.draw_rows(); - queue!(self.editor_contents, cursor::MoveTo(0, 0), cursor::Show)?; + let cursor_x = self.cursor_controller.cursor_x; + let cursor_y = self.cursor_controller.cursor_y; + queue!( + self.editor_contents, + cursor::MoveTo(cursor_x as u16, cursor_y as u16), + cursor::Show + )?; self.editor_contents.flush() } } @@ -92,12 +102,16 @@ impl Editor { } } - fn process_keypress(&self) -> crossterm::Result { + fn process_keypress(&mut self) -> crossterm::Result { match self.reader.read_key()? { KeyEvent { code: KeyCode::Char('q'), - modifiers: event::KeyModifiers::CONTROL, + modifiers: KeyModifiers::CONTROL, } => return Ok(false), + KeyEvent { + code: direction @ (KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right), + modifiers: KeyModifiers::NONE, + } => self.output.move_cursor(direction), _ => {} } Ok(true) @@ -162,6 +176,38 @@ impl io::Write for EditorContents { } } +struct CursorController { + cursor_x: usize, + cursor_y: usize, +} + +impl CursorController { + fn new() -> CursorController { + Self { + cursor_x: 0, + cursor_y: 0, + } + } + + fn move_cursor(&mut self, direction: KeyCode) { + match direction { + KeyCode::Up => { + self.cursor_y -= 1; + } + KeyCode::Left => { + self.cursor_x -= 1; + } + KeyCode::Down => { + self.cursor_y += 1; + } + KeyCode::Right => { + self.cursor_x += 1; + } + _ => unimplemented!(), + } + } +} + fn main() -> crossterm::Result<()> { let _clean_up = CleanUp; terminal::enable_raw_mode()?; From f0292550dc9fcc770d6e888acea50ef8d5f670f0 Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 21:25:57 +0800 Subject: [PATCH 09/15] fix: Fix The Out Of Bounds Error --- src/main.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index d250a5d..a52ca4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ impl Output { Self { win_size, editor_contents: EditorContents::new(), - cursor_controller: CursorController::new(), + cursor_controller: CursorController::new(win_size), } } @@ -124,7 +124,6 @@ impl Editor { } impl Reader { - fn read_key(&self) -> crossterm::Result { loop { if event::poll(Duration::from_millis(500))? { @@ -141,7 +140,6 @@ struct EditorContents { } impl EditorContents { - fn new() -> Self { Self { content: String::new(), @@ -179,29 +177,39 @@ impl io::Write for EditorContents { struct CursorController { cursor_x: usize, cursor_y: usize, + screen_columns: usize, + screen_rows: usize, } impl CursorController { - fn new() -> CursorController { + fn new(win_size: (usize, usize)) -> CursorController { Self { cursor_x: 0, cursor_y: 0, + screen_columns: win_size.0, + screen_rows: win_size.1, } } fn move_cursor(&mut self, direction: KeyCode) { match direction { KeyCode::Up => { - self.cursor_y -= 1; + self.cursor_y = self.cursor_y.saturating_sub(1); } KeyCode::Left => { - self.cursor_x -= 1; + if self.cursor_x != 0 { + self.cursor_x -= 1; + } } KeyCode::Down => { - self.cursor_y += 1; + if self.cursor_y != self.screen_rows - 1 { + self.cursor_y += 1; + } } KeyCode::Right => { - self.cursor_x += 1; + if self.cursor_x != self.screen_columns - 1 { + self.cursor_x += 1; + } } _ => unimplemented!(), } From 49a90ac608d9ae65be281d8c86fb45b9c9b10cf9 Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 21:29:31 +0800 Subject: [PATCH 10/15] feat: Page Up, Page Down, Home And End --- src/main.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index a52ca4f..7c9ff2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -109,9 +109,27 @@ impl Editor { modifiers: KeyModifiers::CONTROL, } => return Ok(false), KeyEvent { - code: direction @ (KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right), + code: + direction + @ + (KeyCode::Up + | KeyCode::Down + | KeyCode::Left + | KeyCode::Right + | KeyCode::Home //add + | KeyCode::End), //add modifiers: KeyModifiers::NONE, } => self.output.move_cursor(direction), + KeyEvent { + code: val @ (KeyCode::PageUp | KeyCode::PageDown), + modifiers: KeyModifiers::NONE, + } => (0..self.output.win_size.1).for_each(|_| { + self.output.move_cursor(if matches!(val, KeyCode::PageUp) { + KeyCode::Up + } else { + KeyCode::Down + }); + }), _ => {} } Ok(true) @@ -211,6 +229,8 @@ impl CursorController { self.cursor_x += 1; } } + KeyCode::End => self.cursor_x = self.screen_columns - 1, + KeyCode::Home => self.cursor_x = 0, _ => unimplemented!(), } } From 847745ca8cc8c759e34c79dad93cd1422ee20847 Mon Sep 17 00:00:00 2001 From: sphcode Date: Sat, 21 Dec 2024 21:44:51 +0800 Subject: [PATCH 11/15] feat: try to read from a file --- src/main.rs | 74 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7c9ff2c..703082c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ use crossterm::event::*; use crossterm::terminal::ClearType; use crossterm::{cursor, event, execute, queue, terminal}; -use std::io; use std::io::{stdout, Write}; +use std::path::Path; use std::time::Duration; -use std::env; +use std::{cmp, env, fs, io}; struct CleanUp; impl Drop for CleanUp { @@ -18,6 +18,7 @@ struct Output { win_size: (usize, usize), editor_contents: EditorContents, cursor_controller: CursorController, + editor_rows: EditorRows, } @@ -30,6 +31,7 @@ impl Output { win_size, editor_contents: EditorContents::new(), cursor_controller: CursorController::new(win_size), + editor_rows: EditorRows::new(), } } @@ -46,22 +48,28 @@ impl Output { let screen_rows = self.win_size.1; let screen_columns = self.win_size.0; for i in 0..screen_rows { - if i == screen_rows / 3 { - let name = env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "Unknown".to_string()); - let version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "Unknown".to_string()); - let mut welcome = format!("{} --- Version {}", name, version); - if welcome.len() > screen_columns { - welcome.truncate(screen_columns) - } - let mut padding = (screen_columns - welcome.len()) / 2; - if padding != 0 { + if i >= self.editor_rows.number_of_rows() { + if self.editor_rows.number_of_rows() == 0 && i == screen_rows / 3 { + let name = env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "Unknown".to_string()); + let version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "Unknown".to_string()); + let mut welcome = format!("{} --- Version {}", name, version); + if welcome.len() > screen_columns { + welcome.truncate(screen_columns) + } + let mut padding = (screen_columns - welcome.len()) / 2; + if padding != 0 { + self.editor_contents.push('~'); + padding -= 1 + } + (0..padding).for_each(|_| self.editor_contents.push(' ')); + self.editor_contents.push_str(&welcome); + } else { self.editor_contents.push('~'); - padding -= 1 } - (0..padding).for_each(|_| self.editor_contents.push(' ')); - self.editor_contents.push_str(&welcome); } else { - self.editor_contents.push('~'); + let len = cmp::min(self.editor_rows.get_row(i).len(), screen_columns); + self.editor_contents + .push_str(&self.editor_rows.get_row(i)[..len]) } queue!( self.editor_contents, @@ -88,6 +96,38 @@ impl Output { } } +struct EditorRows { + row_contents: Vec>, +} + +impl EditorRows { + fn new() -> Self { + let mut arg = env::args(); + + match arg.nth(1) { + None => Self { + row_contents: Vec::new(), + }, + Some(file) => Self::from_file(file.as_ref()), + } + } + + fn from_file(file: &Path) -> Self { + let file_contents = fs::read_to_string(file).expect("Unable to read file"); + Self { + row_contents: file_contents.lines().map(|it| it.into()).collect(), + } + } + + fn number_of_rows(&self) -> usize { + self.row_contents.len() + } + + fn get_row(&self, at:usize) -> &str { + &self.row_contents[at] + } +} + struct Reader; struct Editor { reader: Reader, @@ -195,8 +235,9 @@ impl io::Write for EditorContents { struct CursorController { cursor_x: usize, cursor_y: usize, - screen_columns: usize, screen_rows: usize, + screen_columns: usize, + row_offset: usize, } impl CursorController { @@ -206,6 +247,7 @@ impl CursorController { cursor_y: 0, screen_columns: win_size.0, screen_rows: win_size.1, + row_offset: 0, } } From 84472f7f816fdf9c809a56ea643cc8f93a680172 Mon Sep 17 00:00:00 2001 From: sphcode Date: Sun, 22 Dec 2024 12:29:47 +0800 Subject: [PATCH 12/15] feat: Vertical Scrolling --- src/main.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 703082c..aadc47b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,14 +41,16 @@ impl Output { } fn move_cursor(&mut self, direction: KeyCode) { - self.cursor_controller.move_cursor(direction); + self.cursor_controller + .move_cursor(direction, self.editor_rows.number_of_rows()); } fn draw_rows(&mut self) { let screen_rows = self.win_size.1; let screen_columns = self.win_size.0; for i in 0..screen_rows { - if i >= self.editor_rows.number_of_rows() { + let file_row = i + self.cursor_controller.row_offset; + if file_row >= self.editor_rows.number_of_rows() { if self.editor_rows.number_of_rows() == 0 && i == screen_rows / 3 { let name = env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "Unknown".to_string()); let version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "Unknown".to_string()); @@ -67,9 +69,9 @@ impl Output { self.editor_contents.push('~'); } } else { - let len = cmp::min(self.editor_rows.get_row(i).len(), screen_columns); + let len = cmp::min(self.editor_rows.get_row(file_row).len(), screen_columns); self.editor_contents - .push_str(&self.editor_rows.get_row(i)[..len]) + .push_str(&self.editor_rows.get_row(file_row)[..len]) } queue!( self.editor_contents, @@ -83,10 +85,11 @@ impl Output { } fn refresh_screen(&mut self) -> crossterm::Result<()> { + self.cursor_controller.scroll(); queue!(self.editor_contents, cursor::Hide, cursor::MoveTo(0, 0))?; self.draw_rows(); let cursor_x = self.cursor_controller.cursor_x; - let cursor_y = self.cursor_controller.cursor_y; + let cursor_y = self.cursor_controller.cursor_y - self.cursor_controller.row_offset; queue!( self.editor_contents, cursor::MoveTo(cursor_x as u16, cursor_y as u16), @@ -251,7 +254,14 @@ impl CursorController { } } - fn move_cursor(&mut self, direction: KeyCode) { + fn scroll(&mut self) { + self.row_offset = cmp::min(self.row_offset, self.cursor_y); + if self.cursor_y >= self.row_offset + self.screen_rows { + self.row_offset = self.cursor_y - self.screen_rows + 1; + } + } + + fn move_cursor(&mut self, direction: KeyCode, number_of_rows: usize) { match direction { KeyCode::Up => { self.cursor_y = self.cursor_y.saturating_sub(1); @@ -262,7 +272,7 @@ impl CursorController { } } KeyCode::Down => { - if self.cursor_y != self.screen_rows - 1 { + if self.cursor_y < number_of_rows { self.cursor_y += 1; } } From 84e6757d58060ead76a8f618e07d4f4cc63c594b Mon Sep 17 00:00:00 2001 From: sphcode Date: Sun, 22 Dec 2024 12:42:55 +0800 Subject: [PATCH 13/15] faet: Scrolling --- src/main.rs | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index aadc47b..4b71fe6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use crossterm::event::*; use crossterm::terminal::ClearType; use crossterm::{cursor, event, execute, queue, terminal}; +use std::cmp::Ordering; use std::io::{stdout, Write}; use std::path::Path; use std::time::Duration; @@ -42,7 +43,7 @@ impl Output { fn move_cursor(&mut self, direction: KeyCode) { self.cursor_controller - .move_cursor(direction, self.editor_rows.number_of_rows()); + .move_cursor(direction, &self.editor_rows); } fn draw_rows(&mut self) { @@ -69,9 +70,12 @@ impl Output { self.editor_contents.push('~'); } } else { - let len = cmp::min(self.editor_rows.get_row(file_row).len(), screen_columns); + let row = self.editor_rows.get_row(file_row); + let column_offset = self.cursor_controller.column_offset; + let len = cmp::min(row.len().saturating_sub(column_offset), screen_columns); + let start = if len == 0 { 0 } else { column_offset }; self.editor_contents - .push_str(&self.editor_rows.get_row(file_row)[..len]) + .push_str(&row[start..start + len]); } queue!( self.editor_contents, @@ -88,7 +92,7 @@ impl Output { self.cursor_controller.scroll(); queue!(self.editor_contents, cursor::Hide, cursor::MoveTo(0, 0))?; self.draw_rows(); - let cursor_x = self.cursor_controller.cursor_x; + let cursor_x = self.cursor_controller.cursor_x - self.cursor_controller.column_offset; let cursor_y = self.cursor_controller.cursor_y - self.cursor_controller.row_offset; queue!( self.editor_contents, @@ -241,6 +245,7 @@ struct CursorController { screen_rows: usize, screen_columns: usize, row_offset: usize, + column_offset:usize } impl CursorController { @@ -251,6 +256,7 @@ impl CursorController { screen_columns: win_size.0, screen_rows: win_size.1, row_offset: 0, + column_offset:0 } } @@ -259,9 +265,14 @@ impl CursorController { if self.cursor_y >= self.row_offset + self.screen_rows { self.row_offset = self.cursor_y - self.screen_rows + 1; } + self.column_offset = cmp::min(self.column_offset, self.cursor_x); + if self.cursor_x >= self.column_offset + self.screen_columns { + self.column_offset = self.cursor_x - self.screen_columns + 1; + } } - fn move_cursor(&mut self, direction: KeyCode, number_of_rows: usize) { + fn move_cursor(&mut self, direction: KeyCode, editor_rows: &EditorRows) { + let number_of_rows = editor_rows.number_of_rows(); match direction { KeyCode::Up => { self.cursor_y = self.cursor_y.saturating_sub(1); @@ -270,6 +281,10 @@ impl CursorController { if self.cursor_x != 0 { self.cursor_x -= 1; } + else if self.cursor_y > 0 { + self.cursor_y -= 1; + self.cursor_x = editor_rows.get_row(self.cursor_y).len(); + } } KeyCode::Down => { if self.cursor_y < number_of_rows { @@ -277,14 +292,27 @@ impl CursorController { } } KeyCode::Right => { - if self.cursor_x != self.screen_columns - 1 { - self.cursor_x += 1; + if self.cursor_y < number_of_rows { + match self.cursor_x.cmp(&editor_rows.get_row(self.cursor_y).len()) { + Ordering::Less => self.cursor_x += 1, + Ordering::Equal => { + self.cursor_y += 1; + self.cursor_x = 0 + } + _ => {} + } } } KeyCode::End => self.cursor_x = self.screen_columns - 1, KeyCode::Home => self.cursor_x = 0, _ => unimplemented!(), } + let row_len = if self.cursor_y < number_of_rows { + editor_rows.get_row(self.cursor_y).len() + } else { + 0 + }; + self.cursor_x = cmp::min(self.cursor_x, row_len); } } From 810bf0e41062d9d15aac6571be6ff6af2dd60ceb Mon Sep 17 00:00:00 2001 From: sphcode Date: Sun, 22 Dec 2024 16:47:01 +0800 Subject: [PATCH 14/15] refactor: modularize first step --- src/editor/mod.rs | 316 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 321 +--------------------------------------------- 2 files changed, 319 insertions(+), 318 deletions(-) create mode 100644 src/editor/mod.rs diff --git a/src/editor/mod.rs b/src/editor/mod.rs new file mode 100644 index 0000000..b391dc7 --- /dev/null +++ b/src/editor/mod.rs @@ -0,0 +1,316 @@ +use crossterm::event::*; +use crossterm::terminal::ClearType; +use crossterm::{cursor, event, execute, queue, terminal}; +use std::cmp::Ordering; +use std::io::{stdout, Write}; +use std::path::Path; +use std::time::Duration; +use std::{cmp, env, fs, io}; + +struct Output { + win_size: (usize, usize), + editor_contents: EditorContents, + cursor_controller: CursorController, + editor_rows: EditorRows, +} + +impl Output { + fn new() -> Self { + let win_size = terminal::size() + .map(|(x, y)| (x as usize, y as usize)) + .unwrap(); + Self { + win_size, + editor_contents: EditorContents::new(), + cursor_controller: CursorController::new(win_size), + editor_rows: EditorRows::new(), + } + } + + fn clear_screen() -> crossterm::Result<()> { + execute!(stdout(), terminal::Clear(ClearType::All))?; + execute!(stdout(), cursor::MoveTo(0, 0)) + } + + fn move_cursor(&mut self, direction: KeyCode) { + self.cursor_controller + .move_cursor(direction, &self.editor_rows); + } + + fn draw_rows(&mut self) { + let screen_rows = self.win_size.1; + let screen_columns = self.win_size.0; + for i in 0..screen_rows { + let file_row = i + self.cursor_controller.row_offset; + if file_row >= self.editor_rows.number_of_rows() { + if self.editor_rows.number_of_rows() == 0 && i == screen_rows / 3 { + let name = env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "Unknown".to_string()); + let version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "Unknown".to_string()); + let mut welcome = format!("{} --- Version {}", name, version); + if welcome.len() > screen_columns { + welcome.truncate(screen_columns) + } + let mut padding = (screen_columns - welcome.len()) / 2; + if padding != 0 { + self.editor_contents.push('~'); + padding -= 1 + } + (0..padding).for_each(|_| self.editor_contents.push(' ')); + self.editor_contents.push_str(&welcome); + } else { + self.editor_contents.push('~'); + } + } else { + let row = self.editor_rows.get_row(file_row); + let column_offset = self.cursor_controller.column_offset; + let len = cmp::min(row.len().saturating_sub(column_offset), screen_columns); + let start = if len == 0 { 0 } else { column_offset }; + self.editor_contents + .push_str(&row[start..start + len]); + } + queue!( + self.editor_contents, + terminal::Clear(ClearType::UntilNewLine) + ) + .unwrap(); + if i < screen_rows - 1 { + self.editor_contents.push_str("\r\n"); + } + } + } + + fn refresh_screen(&mut self) -> crossterm::Result<()> { + self.cursor_controller.scroll(); + queue!(self.editor_contents, cursor::Hide, cursor::MoveTo(0, 0))?; + self.draw_rows(); + let cursor_x = self.cursor_controller.cursor_x - self.cursor_controller.column_offset; + let cursor_y = self.cursor_controller.cursor_y - self.cursor_controller.row_offset; + queue!( + self.editor_contents, + cursor::MoveTo(cursor_x as u16, cursor_y as u16), + cursor::Show + )?; + self.editor_contents.flush() + } +} + +struct EditorRows { + row_contents: Vec>, +} + +impl EditorRows { + fn new() -> Self { + let mut arg = env::args(); + + match arg.nth(1) { + None => Self { + row_contents: Vec::new(), + }, + Some(file) => Self::from_file(file.as_ref()), + } + } + + fn from_file(file: &Path) -> Self { + let file_contents = fs::read_to_string(file).expect("Unable to read file"); + Self { + row_contents: file_contents.lines().map(|it| it.into()).collect(), + } + } + + fn number_of_rows(&self) -> usize { + self.row_contents.len() + } + + fn get_row(&self, at:usize) -> &str { + &self.row_contents[at] + } +} + +pub struct Editor { + reader: Reader, + output: Output, +} + +impl Editor { + pub fn new() -> Self { + Self { + reader: Reader, + output: Output::new(), + } + } + + fn process_keypress(&mut self) -> crossterm::Result { + match self.reader.read_key()? { + KeyEvent { + code: KeyCode::Char('q'), + modifiers: KeyModifiers::CONTROL, + } => return Ok(false), + KeyEvent { + code: + direction + @ + (KeyCode::Up + | KeyCode::Down + | KeyCode::Left + | KeyCode::Right + | KeyCode::Home //add + | KeyCode::End), //add + modifiers: KeyModifiers::NONE, + } => self.output.move_cursor(direction), + KeyEvent { + code: val @ (KeyCode::PageUp | KeyCode::PageDown), + modifiers: KeyModifiers::NONE, + } => (0..self.output.win_size.1).for_each(|_| { + self.output.move_cursor(if matches!(val, KeyCode::PageUp) { + KeyCode::Up + } else { + KeyCode::Down + }); + }), + _ => {} + } + Ok(true) + } + + pub fn run(&mut self) -> crossterm::Result { + self.output.refresh_screen()?; + self.process_keypress() + } +} + +impl Drop for Editor { + fn drop(&mut self) { + terminal::disable_raw_mode().expect("Unable to disable raw mode"); + Output::clear_screen().expect("Error"); + } +} + +struct Reader; + +impl Reader { + fn read_key(&self) -> crossterm::Result { + loop { + if event::poll(Duration::from_millis(500))? { + if let Event::Key(event) = event::read()? { + return Ok(event); + } + } + } + } +} + +struct EditorContents { + content: String, +} + +impl EditorContents { + fn new() -> Self { + Self { + content: String::new(), + } + } + + fn push(&mut self, ch: char) { + self.content.push(ch) + } + + fn push_str(&mut self, string: &str) { + self.content.push_str(string) + } +} + +impl io::Write for EditorContents { + fn write(&mut self, buf: &[u8]) -> io::Result { + match std::str::from_utf8(buf) { + Ok(s) => { + self.content.push_str(s); + Ok(s.len()) + } + Err(_) => Err(io::ErrorKind::WriteZero.into()), + } + } + + fn flush(&mut self) -> io::Result<()> { + let out = write!(stdout(), "{}", self.content); + stdout().flush()?; + self.content.clear(); + out + } +} + +struct CursorController { + cursor_x: usize, + cursor_y: usize, + screen_rows: usize, + screen_columns: usize, + row_offset: usize, + column_offset:usize +} + +impl CursorController { + fn new(win_size: (usize, usize)) -> CursorController { + Self { + cursor_x: 0, + cursor_y: 0, + screen_columns: win_size.0, + screen_rows: win_size.1, + row_offset: 0, + column_offset:0 + } + } + + fn scroll(&mut self) { + self.row_offset = cmp::min(self.row_offset, self.cursor_y); + if self.cursor_y >= self.row_offset + self.screen_rows { + self.row_offset = self.cursor_y - self.screen_rows + 1; + } + self.column_offset = cmp::min(self.column_offset, self.cursor_x); + if self.cursor_x >= self.column_offset + self.screen_columns { + self.column_offset = self.cursor_x - self.screen_columns + 1; + } + } + + fn move_cursor(&mut self, direction: KeyCode, editor_rows: &EditorRows) { + let number_of_rows = editor_rows.number_of_rows(); + match direction { + KeyCode::Up => { + self.cursor_y = self.cursor_y.saturating_sub(1); + } + KeyCode::Left => { + if self.cursor_x != 0 { + self.cursor_x -= 1; + } + else if self.cursor_y > 0 { + self.cursor_y -= 1; + self.cursor_x = editor_rows.get_row(self.cursor_y).len(); + } + } + KeyCode::Down => { + if self.cursor_y < number_of_rows { + self.cursor_y += 1; + } + } + KeyCode::Right => { + if self.cursor_y < number_of_rows { + match self.cursor_x.cmp(&editor_rows.get_row(self.cursor_y).len()) { + Ordering::Less => self.cursor_x += 1, + Ordering::Equal => { + self.cursor_y += 1; + self.cursor_x = 0 + } + _ => {} + } + } + } + KeyCode::End => self.cursor_x = self.screen_columns - 1, + KeyCode::Home => self.cursor_x = 0, + _ => unimplemented!(), + } + let row_len = if self.cursor_y < number_of_rows { + editor_rows.get_row(self.cursor_y).len() + } else { + 0 + }; + self.cursor_x = cmp::min(self.cursor_x, row_len); + } +} diff --git a/src/main.rs b/src/main.rs index 4b71fe6..f9f1274 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,325 +1,10 @@ -use crossterm::event::*; -use crossterm::terminal::ClearType; -use crossterm::{cursor, event, execute, queue, terminal}; -use std::cmp::Ordering; -use std::io::{stdout, Write}; -use std::path::Path; -use std::time::Duration; -use std::{cmp, env, fs, io}; -struct CleanUp; +mod editor; -impl Drop for CleanUp { - fn drop(&mut self) { - terminal::disable_raw_mode().expect("Unable to disable raw mode"); - Output::clear_screen().expect("Error"); - } -} - -struct Output { - win_size: (usize, usize), - editor_contents: EditorContents, - cursor_controller: CursorController, - editor_rows: EditorRows, -} - - -impl Output { - fn new() -> Self { - let win_size = terminal::size() - .map(|(x, y)| (x as usize, y as usize)) - .unwrap(); - Self { - win_size, - editor_contents: EditorContents::new(), - cursor_controller: CursorController::new(win_size), - editor_rows: EditorRows::new(), - } - } - - fn clear_screen() -> crossterm::Result<()> { - execute!(stdout(), terminal::Clear(ClearType::All))?; - execute!(stdout(), cursor::MoveTo(0, 0)) - } - - fn move_cursor(&mut self, direction: KeyCode) { - self.cursor_controller - .move_cursor(direction, &self.editor_rows); - } - - fn draw_rows(&mut self) { - let screen_rows = self.win_size.1; - let screen_columns = self.win_size.0; - for i in 0..screen_rows { - let file_row = i + self.cursor_controller.row_offset; - if file_row >= self.editor_rows.number_of_rows() { - if self.editor_rows.number_of_rows() == 0 && i == screen_rows / 3 { - let name = env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "Unknown".to_string()); - let version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "Unknown".to_string()); - let mut welcome = format!("{} --- Version {}", name, version); - if welcome.len() > screen_columns { - welcome.truncate(screen_columns) - } - let mut padding = (screen_columns - welcome.len()) / 2; - if padding != 0 { - self.editor_contents.push('~'); - padding -= 1 - } - (0..padding).for_each(|_| self.editor_contents.push(' ')); - self.editor_contents.push_str(&welcome); - } else { - self.editor_contents.push('~'); - } - } else { - let row = self.editor_rows.get_row(file_row); - let column_offset = self.cursor_controller.column_offset; - let len = cmp::min(row.len().saturating_sub(column_offset), screen_columns); - let start = if len == 0 { 0 } else { column_offset }; - self.editor_contents - .push_str(&row[start..start + len]); - } - queue!( - self.editor_contents, - terminal::Clear(ClearType::UntilNewLine) - ) - .unwrap(); - if i < screen_rows - 1 { - self.editor_contents.push_str("\r\n"); - } - } - } - - fn refresh_screen(&mut self) -> crossterm::Result<()> { - self.cursor_controller.scroll(); - queue!(self.editor_contents, cursor::Hide, cursor::MoveTo(0, 0))?; - self.draw_rows(); - let cursor_x = self.cursor_controller.cursor_x - self.cursor_controller.column_offset; - let cursor_y = self.cursor_controller.cursor_y - self.cursor_controller.row_offset; - queue!( - self.editor_contents, - cursor::MoveTo(cursor_x as u16, cursor_y as u16), - cursor::Show - )?; - self.editor_contents.flush() - } -} - -struct EditorRows { - row_contents: Vec>, -} - -impl EditorRows { - fn new() -> Self { - let mut arg = env::args(); - - match arg.nth(1) { - None => Self { - row_contents: Vec::new(), - }, - Some(file) => Self::from_file(file.as_ref()), - } - } - - fn from_file(file: &Path) -> Self { - let file_contents = fs::read_to_string(file).expect("Unable to read file"); - Self { - row_contents: file_contents.lines().map(|it| it.into()).collect(), - } - } - - fn number_of_rows(&self) -> usize { - self.row_contents.len() - } - - fn get_row(&self, at:usize) -> &str { - &self.row_contents[at] - } -} - -struct Reader; -struct Editor { - reader: Reader, - output: Output, -} - -impl Editor { - fn new() -> Self { - Self { - reader: Reader, - output: Output::new(), - } - } - - fn process_keypress(&mut self) -> crossterm::Result { - match self.reader.read_key()? { - KeyEvent { - code: KeyCode::Char('q'), - modifiers: KeyModifiers::CONTROL, - } => return Ok(false), - KeyEvent { - code: - direction - @ - (KeyCode::Up - | KeyCode::Down - | KeyCode::Left - | KeyCode::Right - | KeyCode::Home //add - | KeyCode::End), //add - modifiers: KeyModifiers::NONE, - } => self.output.move_cursor(direction), - KeyEvent { - code: val @ (KeyCode::PageUp | KeyCode::PageDown), - modifiers: KeyModifiers::NONE, - } => (0..self.output.win_size.1).for_each(|_| { - self.output.move_cursor(if matches!(val, KeyCode::PageUp) { - KeyCode::Up - } else { - KeyCode::Down - }); - }), - _ => {} - } - Ok(true) - } - - fn run(&mut self) -> crossterm::Result { - self.output.refresh_screen()?; - self.process_keypress() - } -} - -impl Reader { - fn read_key(&self) -> crossterm::Result { - loop { - if event::poll(Duration::from_millis(500))? { - if let Event::Key(event) = event::read()? { - return Ok(event); - } - } - } - } -} - -struct EditorContents { - content: String, -} - -impl EditorContents { - fn new() -> Self { - Self { - content: String::new(), - } - } - - fn push(&mut self, ch: char) { - self.content.push(ch) - } - - fn push_str(&mut self, string: &str) { - self.content.push_str(string) - } -} - -impl io::Write for EditorContents { - fn write(&mut self, buf: &[u8]) -> io::Result { - match std::str::from_utf8(buf) { - Ok(s) => { - self.content.push_str(s); - Ok(s.len()) - } - Err(_) => Err(io::ErrorKind::WriteZero.into()), - } - } - - fn flush(&mut self) -> io::Result<()> { - let out = write!(stdout(), "{}", self.content); - stdout().flush()?; - self.content.clear(); - out - } -} - -struct CursorController { - cursor_x: usize, - cursor_y: usize, - screen_rows: usize, - screen_columns: usize, - row_offset: usize, - column_offset:usize -} - -impl CursorController { - fn new(win_size: (usize, usize)) -> CursorController { - Self { - cursor_x: 0, - cursor_y: 0, - screen_columns: win_size.0, - screen_rows: win_size.1, - row_offset: 0, - column_offset:0 - } - } - - fn scroll(&mut self) { - self.row_offset = cmp::min(self.row_offset, self.cursor_y); - if self.cursor_y >= self.row_offset + self.screen_rows { - self.row_offset = self.cursor_y - self.screen_rows + 1; - } - self.column_offset = cmp::min(self.column_offset, self.cursor_x); - if self.cursor_x >= self.column_offset + self.screen_columns { - self.column_offset = self.cursor_x - self.screen_columns + 1; - } - } - - fn move_cursor(&mut self, direction: KeyCode, editor_rows: &EditorRows) { - let number_of_rows = editor_rows.number_of_rows(); - match direction { - KeyCode::Up => { - self.cursor_y = self.cursor_y.saturating_sub(1); - } - KeyCode::Left => { - if self.cursor_x != 0 { - self.cursor_x -= 1; - } - else if self.cursor_y > 0 { - self.cursor_y -= 1; - self.cursor_x = editor_rows.get_row(self.cursor_y).len(); - } - } - KeyCode::Down => { - if self.cursor_y < number_of_rows { - self.cursor_y += 1; - } - } - KeyCode::Right => { - if self.cursor_y < number_of_rows { - match self.cursor_x.cmp(&editor_rows.get_row(self.cursor_y).len()) { - Ordering::Less => self.cursor_x += 1, - Ordering::Equal => { - self.cursor_y += 1; - self.cursor_x = 0 - } - _ => {} - } - } - } - KeyCode::End => self.cursor_x = self.screen_columns - 1, - KeyCode::Home => self.cursor_x = 0, - _ => unimplemented!(), - } - let row_len = if self.cursor_y < number_of_rows { - editor_rows.get_row(self.cursor_y).len() - } else { - 0 - }; - self.cursor_x = cmp::min(self.cursor_x, row_len); - } -} +use crossterm::terminal; fn main() -> crossterm::Result<()> { - let _clean_up = CleanUp; terminal::enable_raw_mode()?; - let mut editor = Editor::new(); + let mut editor = editor::Editor::new(); while editor.run()? {} Ok(()) } \ No newline at end of file From 21a4b5505bd33c70af296dce8ddbea82fdc1fee6 Mon Sep 17 00:00:00 2001 From: sphcode Date: Sun, 22 Dec 2024 17:39:51 +0800 Subject: [PATCH 15/15] refactor: second step --- src/editor/cursor_controller.rs | 83 +++++++++++++++ src/editor/editor_contents.rs | 41 ++++++++ src/editor/editor_rows.rs | 34 ++++++ src/editor/mod.rs | 180 +++----------------------------- src/editor/reader.rs | 16 +++ 5 files changed, 186 insertions(+), 168 deletions(-) create mode 100644 src/editor/cursor_controller.rs create mode 100644 src/editor/editor_contents.rs create mode 100644 src/editor/editor_rows.rs create mode 100644 src/editor/reader.rs diff --git a/src/editor/cursor_controller.rs b/src/editor/cursor_controller.rs new file mode 100644 index 0000000..b4de3e4 --- /dev/null +++ b/src/editor/cursor_controller.rs @@ -0,0 +1,83 @@ +use std::cmp; +use std::cmp::Ordering; + +use crossterm::event::KeyCode; +use crate::editor::EditorRows; + + +pub struct CursorController { + pub cursor_x: usize, + pub cursor_y: usize, + screen_rows: usize, + screen_columns: usize, + pub row_offset: usize, + pub column_offset:usize +} + +impl CursorController { + pub fn new(win_size: (usize, usize)) -> CursorController { + Self { + cursor_x: 0, + cursor_y: 0, + screen_columns: win_size.0, + screen_rows: win_size.1, + row_offset: 0, + column_offset:0 + } + } + + pub fn scroll(&mut self) { + self.row_offset = cmp::min(self.row_offset, self.cursor_y); + if self.cursor_y >= self.row_offset + self.screen_rows { + self.row_offset = self.cursor_y - self.screen_rows + 1; + } + self.column_offset = cmp::min(self.column_offset, self.cursor_x); + if self.cursor_x >= self.column_offset + self.screen_columns { + self.column_offset = self.cursor_x - self.screen_columns + 1; + } + } + + pub fn move_cursor(&mut self, direction: KeyCode, editor_rows: &EditorRows) { + let number_of_rows = editor_rows.number_of_rows(); + match direction { + KeyCode::Up => { + self.cursor_y = self.cursor_y.saturating_sub(1); + } + KeyCode::Left => { + if self.cursor_x != 0 { + self.cursor_x -= 1; + } + else if self.cursor_y > 0 { + self.cursor_y -= 1; + self.cursor_x = editor_rows.get_row(self.cursor_y).len(); + } + } + KeyCode::Down => { + if self.cursor_y < number_of_rows { + self.cursor_y += 1; + } + } + KeyCode::Right => { + if self.cursor_y < number_of_rows { + match self.cursor_x.cmp(&editor_rows.get_row(self.cursor_y).len()) { + Ordering::Less => self.cursor_x += 1, + Ordering::Equal => { + self.cursor_y += 1; + self.cursor_x = 0 + } + _ => {} + } + } + } + KeyCode::End => self.cursor_x = self.screen_columns - 1, + KeyCode::Home => self.cursor_x = 0, + _ => unimplemented!(), + } + let row_len = if self.cursor_y < number_of_rows { + editor_rows.get_row(self.cursor_y).len() + } else { + 0 + }; + self.cursor_x = cmp::min(self.cursor_x, row_len); + } +} diff --git a/src/editor/editor_contents.rs b/src/editor/editor_contents.rs new file mode 100644 index 0000000..011ac7c --- /dev/null +++ b/src/editor/editor_contents.rs @@ -0,0 +1,41 @@ +use std::io; +use std::io::stdout; + +pub struct EditorContents { + content: String, +} + +impl EditorContents { + pub fn new() -> Self { + Self { + content: String::new(), + } + } + + pub fn push(&mut self, ch: char) { + self.content.push(ch) + } + + pub fn push_str(&mut self, string: &str) { + self.content.push_str(string) + } +} + +impl io::Write for EditorContents { + fn write(&mut self, buf: &[u8]) -> io::Result { + match std::str::from_utf8(buf) { + Ok(s) => { + self.content.push_str(s); + Ok(s.len()) + } + Err(_) => Err(io::ErrorKind::WriteZero.into()), + } + } + + fn flush(&mut self) -> io::Result<()> { + let out = write!(stdout(), "{}", self.content); + stdout().flush()?; + self.content.clear(); + out + } +} \ No newline at end of file diff --git a/src/editor/editor_rows.rs b/src/editor/editor_rows.rs new file mode 100644 index 0000000..1078e5e --- /dev/null +++ b/src/editor/editor_rows.rs @@ -0,0 +1,34 @@ +use std::{env, fs}; +use std::path::Path; + +pub struct EditorRows { + row_contents: Vec>, +} + +impl EditorRows { + pub fn new() -> Self { + let mut arg = env::args(); + + match arg.nth(1) { + None => Self { + row_contents: Vec::new(), + }, + Some(file) => Self::from_file(file.as_ref()), + } + } + + fn from_file(file: &Path) -> Self { + let file_contents = fs::read_to_string(file).expect("Unable to read file"); + Self { + row_contents: file_contents.lines().map(|it| it.into()).collect(), + } + } + + pub fn number_of_rows(&self) -> usize { + self.row_contents.len() + } + + pub fn get_row(&self, at:usize) -> &str { + &self.row_contents[at] + } +} \ No newline at end of file diff --git a/src/editor/mod.rs b/src/editor/mod.rs index b391dc7..b17a576 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,11 +1,17 @@ +mod editor_contents; +mod editor_rows; +mod cursor_controller; +mod reader; + +use editor_contents::EditorContents; +use editor_rows::EditorRows; +use cursor_controller::CursorController; +use reader::Reader; use crossterm::event::*; use crossterm::terminal::ClearType; -use crossterm::{cursor, event, execute, queue, terminal}; -use std::cmp::Ordering; +use crossterm::{cursor, execute, queue, terminal}; +use std::{cmp, env}; use std::io::{stdout, Write}; -use std::path::Path; -use std::time::Duration; -use std::{cmp, env, fs, io}; struct Output { win_size: (usize, usize), @@ -94,38 +100,6 @@ impl Output { } } -struct EditorRows { - row_contents: Vec>, -} - -impl EditorRows { - fn new() -> Self { - let mut arg = env::args(); - - match arg.nth(1) { - None => Self { - row_contents: Vec::new(), - }, - Some(file) => Self::from_file(file.as_ref()), - } - } - - fn from_file(file: &Path) -> Self { - let file_contents = fs::read_to_string(file).expect("Unable to read file"); - Self { - row_contents: file_contents.lines().map(|it| it.into()).collect(), - } - } - - fn number_of_rows(&self) -> usize { - self.row_contents.len() - } - - fn get_row(&self, at:usize) -> &str { - &self.row_contents[at] - } -} - pub struct Editor { reader: Reader, output: Output, @@ -183,134 +157,4 @@ impl Drop for Editor { terminal::disable_raw_mode().expect("Unable to disable raw mode"); Output::clear_screen().expect("Error"); } -} - -struct Reader; - -impl Reader { - fn read_key(&self) -> crossterm::Result { - loop { - if event::poll(Duration::from_millis(500))? { - if let Event::Key(event) = event::read()? { - return Ok(event); - } - } - } - } -} - -struct EditorContents { - content: String, -} - -impl EditorContents { - fn new() -> Self { - Self { - content: String::new(), - } - } - - fn push(&mut self, ch: char) { - self.content.push(ch) - } - - fn push_str(&mut self, string: &str) { - self.content.push_str(string) - } -} - -impl io::Write for EditorContents { - fn write(&mut self, buf: &[u8]) -> io::Result { - match std::str::from_utf8(buf) { - Ok(s) => { - self.content.push_str(s); - Ok(s.len()) - } - Err(_) => Err(io::ErrorKind::WriteZero.into()), - } - } - - fn flush(&mut self) -> io::Result<()> { - let out = write!(stdout(), "{}", self.content); - stdout().flush()?; - self.content.clear(); - out - } -} - -struct CursorController { - cursor_x: usize, - cursor_y: usize, - screen_rows: usize, - screen_columns: usize, - row_offset: usize, - column_offset:usize -} - -impl CursorController { - fn new(win_size: (usize, usize)) -> CursorController { - Self { - cursor_x: 0, - cursor_y: 0, - screen_columns: win_size.0, - screen_rows: win_size.1, - row_offset: 0, - column_offset:0 - } - } - - fn scroll(&mut self) { - self.row_offset = cmp::min(self.row_offset, self.cursor_y); - if self.cursor_y >= self.row_offset + self.screen_rows { - self.row_offset = self.cursor_y - self.screen_rows + 1; - } - self.column_offset = cmp::min(self.column_offset, self.cursor_x); - if self.cursor_x >= self.column_offset + self.screen_columns { - self.column_offset = self.cursor_x - self.screen_columns + 1; - } - } - - fn move_cursor(&mut self, direction: KeyCode, editor_rows: &EditorRows) { - let number_of_rows = editor_rows.number_of_rows(); - match direction { - KeyCode::Up => { - self.cursor_y = self.cursor_y.saturating_sub(1); - } - KeyCode::Left => { - if self.cursor_x != 0 { - self.cursor_x -= 1; - } - else if self.cursor_y > 0 { - self.cursor_y -= 1; - self.cursor_x = editor_rows.get_row(self.cursor_y).len(); - } - } - KeyCode::Down => { - if self.cursor_y < number_of_rows { - self.cursor_y += 1; - } - } - KeyCode::Right => { - if self.cursor_y < number_of_rows { - match self.cursor_x.cmp(&editor_rows.get_row(self.cursor_y).len()) { - Ordering::Less => self.cursor_x += 1, - Ordering::Equal => { - self.cursor_y += 1; - self.cursor_x = 0 - } - _ => {} - } - } - } - KeyCode::End => self.cursor_x = self.screen_columns - 1, - KeyCode::Home => self.cursor_x = 0, - _ => unimplemented!(), - } - let row_len = if self.cursor_y < number_of_rows { - editor_rows.get_row(self.cursor_y).len() - } else { - 0 - }; - self.cursor_x = cmp::min(self.cursor_x, row_len); - } -} +} \ No newline at end of file diff --git a/src/editor/reader.rs b/src/editor/reader.rs new file mode 100644 index 0000000..dfe7a3b --- /dev/null +++ b/src/editor/reader.rs @@ -0,0 +1,16 @@ +use std::time::Duration; +use crossterm::event::{self, Event, KeyEvent}; + +pub struct Reader; + +impl Reader { + pub fn read_key(&self) -> crossterm::Result { + loop { + if event::poll(Duration::from_millis(500))? { + if let Event::Key(event) = event::read()? { + return Ok(event); + } + } + } + } +} \ No newline at end of file