diff --git a/Cargo.lock b/Cargo.lock index 705b95c..711e4a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,6 +36,7 @@ dependencies = [ name = "rs-kilo" version = "0.1.0" dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index e1e20f3..91da894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,6 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +libc = "0.2" # Rust wrappers for C/POSIX headers nix = "0.15.0" diff --git a/src/editor.rs b/src/editor.rs index e8aa139..ba80492 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,23 +1,28 @@ //! Editor functionality use crate::helpers::*; -use nix::sys::termios::Termios; -use nix::unistd::read; - use std::io; use std::io::prelude::*; -use std::io::{BufReader, Error, Stdin}; -use std::str::Chars; +use std::io::BufReader; /// Main structure for the editor /// /// impl blocks are split similarly to the original C implementation -pub struct Editor {} +#[derive(Debug, Default)] +pub struct Editor { + screen_cols: u16, + screen_rows: u16, +} // init impl Editor { pub fn new() -> Self { - Editor {} + let mut instance = Self::default(); + let size = instance.get_window_size(); + instance.screen_cols = size.cols; + instance.screen_rows = size.rows; + + instance } } @@ -42,10 +47,41 @@ impl Editor { return Some(output); } + + fn get_cursor_position(&mut self) -> TermSize { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + let buffer = String::from("\x1b[6n").into_bytes(); + handle.write(&buffer).unwrap(); + + let stdin = io::stdin(); + let stdin = stdin.lock(); + let mut in_buf = String::new().into_bytes(); + let mut input = BufReader::with_capacity(32, stdin); + input.read_until('R' as u8, &mut in_buf).unwrap(); + + // @TODO Find a solution to retrieve the cursor coordinates + unimplemented!(); + } + + fn get_window_size(&mut self) -> TermSize { + match get_term_size() { + Some(size) => size, + None => { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + let buffer = String::from("\x1b[999C\x1b[999B").into_bytes(); + handle.write(&buffer).unwrap(); + + self.get_cursor_position() + } + } + } } // Input impl Editor { + /// Route user input to the appropriate handler method pub fn process_keypress(&mut self) -> Option<()> { match self.read_key() { // Just continue the input loop on an "empty" keypress @@ -54,6 +90,8 @@ impl Editor { let first = chars[0]; if first == ctrl_key('q') { + clear_and_reset(); + // Break out of the input loop return None; } @@ -69,11 +107,29 @@ impl Editor { // Output impl Editor { - pub fn refresh_screen() { + fn draw_rows(&mut self) { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + let buffer = String::from("~\r\n").into_bytes(); + + for _ in 0..self.screen_rows as usize { + handle.write(&buffer).unwrap(); + } + } + + pub fn refresh_screen(&mut self) { let stdout = io::stdout(); let mut handle = stdout.lock(); + // Clear screen let mut buffer = String::from("\x1b[2J").into_bytes(); + handle.write_all(&mut buffer).unwrap(); + + // Reposition cursor + let mut buffer = String::from("\x1b[H").into_bytes(); + handle.write_all(&mut buffer).unwrap(); + + self.draw_rows(); handle.write_all(&mut buffer).unwrap(); } diff --git a/src/helpers.rs b/src/helpers.rs index 13efbd3..9811da2 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,15 +1,37 @@ +use libc::ioctl; /// Helper functions, especially to reproduce C std/posix functions +use libc::{c_ushort, STDOUT_FILENO, TIOCGWINSZ}; + use nix::errno::*; use nix::sys::termios; use nix::sys::termios::{ ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios, }; -use nix::Error as NixError; -use std::io::Error; +use std::io; +use std::io::prelude::*; use std::os::unix::io::RawFd; use std::process::exit; +#[derive(Debug)] +pub struct TermSize { + /// number of rows + pub rows: u16, + /// number of columns + pub cols: u16, +} + +#[repr(C)] +#[derive(Debug)] +struct UnixTermSize { + /// number of rows + pub rows: c_ushort, + /// number of columns + pub cols: c_ushort, + x: c_ushort, + y: c_ushort, +} + // Redefine the posix constants for rust land /// The value of the raw file descriptor for STDIN @@ -33,6 +55,8 @@ pub fn ctrl_key(c: char) -> char { } pub fn die(code: &Errno, msg: &str) -> ! { + clear_and_reset(); + eprintln!("{:?} ({})", code, msg); exit(1) @@ -86,3 +110,36 @@ pub fn enable_raw_mode() { pub fn disable_raw_mode(original: &Termios) { termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, original).unwrap(); } + +pub fn clear_and_reset() { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + + // Clear screen + let mut buffer = String::from("\x1b[2J").into_bytes(); + handle.write_all(&mut buffer).unwrap(); + + // Reposition cursor + let mut buffer = String::from("\x1b[H").into_bytes(); + handle.write_all(&mut buffer).unwrap(); +} + +pub fn get_term_size() -> Option { + let raw = UnixTermSize { + rows: 0, + cols: 0, + x: 0, + y: 0, + }; + + let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &raw) }; + + if r == 0 { + Some(TermSize { + rows: raw.rows, + cols: raw.cols, + }) + } else { + None + } +} diff --git a/src/main.rs b/src/main.rs index 4025c8c..b1f7ac5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,8 +17,12 @@ fn main() { // Main input loop. Editor::process_keypress uses an Option Enum as a sentinel. // `None` is returned on a quit action, in other cases, `Some(())` is returned, // continuing the loop - while editor.process_keypress().is_some() { - // loop + loop { + editor.refresh_screen(); + + if editor.process_keypress().is_none() { + break; + } } // Restore previous terminal flags before exit