From 7ca43985cd18c41e6a517700f28c00050c0a0e8e Mon Sep 17 00:00:00 2001 From: shepherdma Date: Wed, 29 Nov 2023 23:54:06 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84=E6=95=B4?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 20 +++++++++ Cargo.lock | 58 +++++++++++++++++++++++++ Cargo.toml | 9 ++++ src/document.rs | 20 +++++++++ src/editor.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 18 ++++++++ src/row.rs | 20 +++++++++ src/terminal.rs | 62 +++++++++++++++++++++++++++ 8 files changed, 319 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/document.rs create mode 100644 src/editor.rs create mode 100644 src/main.rs create mode 100644 src/row.rs create mode 100644 src/terminal.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5f59d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Compiled files +/target/ + +# These directories store cache and intermediate files +**/*.rs.bk + +# IDE - IntelliJ +.idea/ + +# Dependency directory +/.cargo/ + +# Rustup directory +/.rustup/ + +# Environment variable file +.env + +# Ignore .DS_Store files (Mac OS) +.DS_Store diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a1475fe --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,58 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "hecto" +version = "0.1.0" +dependencies = [ + "termion", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_termios" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" +dependencies = [ + "redox_syscall", +] + +[[package]] +name = "termion" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" +dependencies = [ + "libc", + "numtoa", + "redox_syscall", + "redox_termios", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f383978 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hecto" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +termion = "1" diff --git a/src/document.rs b/src/document.rs new file mode 100644 index 0000000..458e985 --- /dev/null +++ b/src/document.rs @@ -0,0 +1,20 @@ +use crate::Row; + +#[derive(Default)] +pub struct Document { + rows: Vec, +} + +impl Document { + pub fn open() -> Self{ + let mut rows = Vec::new(); + rows.push(Row::from("Hello, World!")); + Self { rows } + } + pub fn row(&self, index: usize) -> Option<&Row> { + self.rows.get(index) + } + pub fn is_empty(&self) -> bool { + self.rows.is_empty() + } +} diff --git a/src/editor.rs b/src/editor.rs new file mode 100644 index 0000000..1e442cd --- /dev/null +++ b/src/editor.rs @@ -0,0 +1,112 @@ +use crate::Row; +use crate::Document; +use crate::Terminal; +use termion::event::Key; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); +#[derive(Default)] +pub struct Position { + pub x: usize, + pub y: usize, +} +pub struct Editor { + should_quit: bool, + terminal: Terminal, + cursor_position: Position, + document: Document, +} +fn die(e: &std::io::Error) { + Terminal::clear_screen(); + panic!("{}", e) +} + +impl Editor { + pub fn run(&mut self) { + loop { + if let Err(error) = self.refresh_screen() { + die(&error); + } + if self.should_quit { + println!("Goodbye.\r"); + break; + } + if let Err(error) = self.process_keypress() { + die(&error); + } + } + } + pub fn default() -> Self{ + Self{ + should_quit: false, + terminal: Terminal::default().expect("Failed to initialize terminal"), + cursor_position: Position::default(), + document: Document::open(), + } + } + fn process_keypress(&mut self) -> Result<(), std::io::Error> { + let pressed_key = Terminal::read_key()?; + match pressed_key { + Key::Ctrl('q') => self.should_quit = true, + Key::Up | Key::Down | Key::Left | Key::Right => self.move_cursor(pressed_key), + _ => (), + } + Ok(()) + } + + fn move_cursor(&mut self, key: Key) { + let Position { mut y, mut x} = self.cursor_position; + match key { + Key::Up => y = y.saturating_sub(1), + Key::Down => y = y.saturating_add(1), + Key::Right => x = x.saturating_add(1), + Key::Left => x = x.saturating_sub(1), + _ => (), + } + self.cursor_position = Position { x, y } + } + + fn refresh_screen(&self) -> Result<(), std::io::Error> { + // print!("\x1b[2J"); + Terminal::cursor_hide(); + Terminal::clear_screen(); + Terminal::cursor_position(&Position::default()); + if self.should_quit { + println!("Goodbye.\r"); + } else { + self.draw_rows(); + Terminal::cursor_position(&self.cursor_position); + } + Terminal::cursor_show(); + Terminal::flush() + } + fn draw_rows(&self) { + let height = self.terminal.size().height; + for terminal_row in 0..height - 1 { + Terminal::clear_current_line(); + if let Some(row) = self.document.row(terminal_row as usize) { + self.draw_row(row) + } else if self.document.is_empty() && terminal_row == height / 3 { + self.draw_welcome_message(); + } else { + println!("~\r"); + } + } + } + fn draw_welcome_message(&self) { + let mut welcome_message = format!("Hecto editor --version{}", VERSION); + let width = self.terminal.size().width as usize; + let len = welcome_message.len(); + let padding = width.saturating_sub(len) / 2; + let spaces = " ".repeat(padding.saturating_sub(1)); + welcome_message = format!("~{}{}", spaces, welcome_message); + welcome_message.truncate(width); + println!("{}\r", welcome_message); + } + pub fn draw_row(&self, row: &Row) { + let start = 0; + let end = self.terminal.size().width as usize; + let row = row.render(start, end); + println!("{}\r", row); + } + +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4348f57 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,18 @@ +#![warn(clippy::all, clippy::pedantic)] +mod row; +mod editor; +mod document; +mod terminal; + +use editor::Editor; +pub use terminal::Terminal; +pub use document::Document; +pub use row::Row; + + +fn main() { + println!("Welcome to Hecto Text editor"); + let mut editor = Editor::default(); + editor.run(); +} + diff --git a/src/row.rs b/src/row.rs new file mode 100644 index 0000000..6220d36 --- /dev/null +++ b/src/row.rs @@ -0,0 +1,20 @@ +use std::cmp; +pub struct Row { + string: String, +} + +impl From<&str> for Row { + fn from(slice: &str) -> Self { + Self { + string: String::from(slice), + } + } +} + +impl Row { + pub fn render(&self, start: usize, end: usize) -> String { + let end = cmp::min(end, self.string.len()); + let start = cmp::min(start, end); + self.string.get(start..end).unwrap_or_default().to_string() + } +} diff --git a/src/terminal.rs b/src/terminal.rs new file mode 100644 index 0000000..9c4195b --- /dev/null +++ b/src/terminal.rs @@ -0,0 +1,62 @@ +use crate::editor::Position; +use std::io::{self, stdout, Write}; +use termion::event::Key; +use termion::input::TermRead; +use termion::raw::{IntoRawMode, RawTerminal}; + +pub struct Size { + pub width: u16, + pub height: u16, +} + +pub struct Terminal { + size: Size, + _stdout: RawTerminal, +} + +impl Terminal { + pub fn default() -> Result { + let size = termion::terminal_size()?; + Ok(Self { + size: Size { + width: size.0, + height: size.1, + }, + _stdout: stdout().into_raw_mode()?, + }) + } + pub fn size (&self) -> &Size { + &self.size + } + pub fn clear_screen() { + print!("{}", termion::clear::All); + } + pub fn cursor_position(position: &Position) { + let Position{mut x, mut y} = position; + x = x.saturating_add(1); + y = y.saturating_add(1); + let x = x as u16; + let y = y as u16; + print!("{}", termion::cursor::Goto(x, y)); + } + pub fn read_key() -> Result { + loop { + if let Some(key) = io::stdin().lock().keys().next() { + return key; + } + } + } + pub fn flush() -> Result<(), std::io::Error> { + io::stdout().flush() + } + pub fn cursor_hide() { + print!("{}", termion::cursor::Hide); + } + pub fn cursor_show() { + print!("{}", termion::cursor::Show); + } + pub fn clear_current_line() { + print!("{}", termion::clear::CurrentLine); + } +} +