diff --git a/src/editor.rs b/src/editor.rs index 7b1a454..c983406 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,6 +1,7 @@ //! Editor functionality use crate::helpers::*; +use std::cmp::PartialEq; use std::io; use std::io::prelude::*; use std::io::BufReader; @@ -10,16 +11,46 @@ use std::io::BufReader; /// impl blocks are split similarly to the original C implementation #[derive(Debug, Default)] pub struct Editor { + cursor_x: usize, + cursor_y: usize, screen_cols: usize, screen_rows: usize, output_buffer: String, } +#[derive(Copy, Clone, Debug, PartialEq)] +enum EditorKey { + Escape, + ArrowLeft, + ArrowRight, + ArrowUp, + ArrowDown, + DeleteKey, + HomeKey, + EndKey, + PageUp, + PageDown, + OtherKey(T), +} + +impl EditorKey { + pub fn unwrap(self) -> char { + match self { + self::OtherKey(val) => val, + _ => panic!("called `EditorKey::unwrap()` on a `None` value"), + } + } +} + +use self::EditorKey::*; + // init impl Editor { pub fn new() -> Self { let mut instance = Self::default(); let size = instance.get_window_size(); + instance.cursor_x = 0; + instance.cursor_y = 0; instance.screen_cols = size.cols as usize; instance.screen_rows = size.rows as usize; @@ -29,78 +60,158 @@ impl Editor { // Terminal impl Editor { - fn read_key(&mut self) -> Option> { + fn read_key(&mut self) -> Option>> { let stdin = io::stdin(); let stdin = stdin.lock(); let mut in_str = String::new(); let mut input = BufReader::with_capacity(3, stdin); input.read_to_string(&mut in_str).unwrap(); - let mut output: Vec = vec![]; + let mut output: Vec> = vec![]; for char in in_str.chars() { - output.push(char); + output.push(match char { + '\x1b' => Escape, + _ => OtherKey(char), + }); } if output.len() == 0 { return None; } + if output[0].eq(&Escape) { + if output.len() == 4 { + if output[3].eq(&OtherKey('~')) { + let action = match output[2].unwrap() { + '1' => HomeKey, + '3' => DeleteKey, + '4' => EndKey, + '5' => PageUp, + '6' => PageDown, + '7' => HomeKey, + '8' => EndKey, + _ => Escape, + }; + + return Some(vec![action]); + } + } + if output[1].eq(&OtherKey('[')) { + let action = match output[2] { + OtherKey('A') => ArrowUp, + OtherKey('B') => ArrowDown, + OtherKey('C') => ArrowRight, + OtherKey('D') => ArrowLeft, + OtherKey('H') => HomeKey, + OtherKey('F') => EndKey, + + // Eh, just return escape otherwise + _ => return Some(vec![Escape]), + }; + + return Some(vec![action]); + } + if output[1].eq(&OtherKey('O')) { + let action = match output[2] { + OtherKey('H') => HomeKey, + OtherKey('F') => EndKey, + _ => Escape, + }; + + return Some(vec![action]); + } + } + + + 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() - } + None => unimplemented!("The easy way usually works") } } } // Input impl Editor { + fn move_cursor(&mut self, key: &EditorKey) { + match key { + ArrowLeft => { + if self.cursor_x != 0 { + self.cursor_x -= 1; + } + }, + ArrowRight => { + if self.cursor_x != self.screen_cols - 1 { + self.cursor_x += 1; + } + }, + ArrowUp => { + if self.cursor_y != 0 { + self.cursor_y -= 1; + } + }, + ArrowDown => { + if self.cursor_y != self.screen_rows - 1 { + self.cursor_y += 1; + } + }, + _ => (), + }; + } + /// 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 - None => Some(()), - Some(chars) => { - let first = chars[0]; + let chars = self.read_key(); + if chars.is_none() { + Some(()) // Continue input loop on empty input + } else { + let chars = chars.unwrap(); + // print!("{:?}\r\n", &chars); + let first = &chars[0]; - if first == ctrl_key('q') { - clear_and_reset(); + match first { + OtherKey(c) => { + if c == &ctrl_key('q') { + print!("\x1b[2J"); + print!("\x1b[H"); + // Break out of the input loop + return None; + } + }, + HomeKey => { + self.cursor_x = 0; + }, + EndKey => { + self.cursor_x = self.screen_cols - 1; + }, + PageUp => self.page_up_or_down(PageUp), + PageDown => self.page_up_or_down(PageDown), + ArrowUp => self.move_cursor(&ArrowUp), + ArrowDown => self.move_cursor(&ArrowDown), + ArrowLeft => self.move_cursor(&ArrowLeft), + ArrowRight => self.move_cursor(&ArrowRight), + _ => (), + }; - // Break out of the input loop - return None; - } + // Continue the main input loop + Some(()) + } + } - print!("{:?}\r\n", chars); + fn page_up_or_down(&mut self, key: EditorKey) { + let mut times = self.screen_rows; - // Continue the main input loop - Some(()) + while times > 1 { + times -= 1; + match key { + PageUp => self.move_cursor(&ArrowUp), + PageDown => self.move_cursor(&ArrowDown), + _ => (), } } } @@ -140,28 +251,31 @@ impl Editor { self.append_out("~"); } - self.append_out("\x1b[K"); - if y < (self.screen_rows as usize - 1) { + if y < (self.screen_rows - 1) { self.append_out("\r\n"); } } } - pub fn refresh_screen(&mut self) { + pub fn refresh_screen(&mut self) -> io::Result<()> { self.output_buffer.clear(); // Hide cursor, reposition cursor - self.append_out("\x1b[?25l"); - self.append_out("\x1b[H"); + //self.append_out("\x1b[?25l"); + //self.append_out("\x1b[H"); self.draw_rows(); - self.append_out("\x1b[H"); + // Move cursor to state position + let cursor_code = format!("\x1b[{};{}H", self.cursor_y + 1, self.cursor_x + 1); + self.append_out(&cursor_code); + + // Show cursor self.append_out("\x1b[?25h"); let stdout = io::stdout(); let mut handle = stdout.lock(); - handle.write_all(&self.output_buffer.as_bytes()).unwrap(); + handle.write_all(&self.output_buffer.as_bytes()) } } diff --git a/src/helpers.rs b/src/helpers.rs index 9811da2..bbc3ed2 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,17 +1,10 @@ use libc::ioctl; -/// Helper functions, especially to reproduce C std/posix functions -use libc::{c_ushort, STDOUT_FILENO, TIOCGWINSZ}; - -use nix::errno::*; +use libc::{c_ushort, STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ}; use nix::sys::termios; use nix::sys::termios::{ ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios, }; - -use std::io; -use std::io::prelude::*; use std::os::unix::io::RawFd; -use std::process::exit; #[derive(Debug)] pub struct TermSize { @@ -32,13 +25,6 @@ struct UnixTermSize { y: c_ushort, } -// Redefine the posix constants for rust land - -/// The value of the raw file descriptor for STDIN -pub const STDIN_FILENO: i32 = 0; -// pub const STDOUT_FILENO: i32 = 1; -// pub const STDERR_FILENO: i32 = 2; - /// Convert Ctrl+letter chords to their /// ASCII table equivalents #[inline] @@ -54,14 +40,6 @@ pub fn ctrl_key(c: char) -> char { (key & 0x1f) as char } -pub fn die(code: &Errno, msg: &str) -> ! { - clear_and_reset(); - - eprintln!("{:?} ({})", code, msg); - - exit(1) -} - /// Get a `Termios` struct, for getting/setting terminal flags pub fn get_termios(fd: RawFd) -> Termios { termios::tcgetattr(fd).unwrap() @@ -111,19 +89,6 @@ 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, diff --git a/src/main.rs b/src/main.rs index b1f7ac5..7c9adb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,11 @@ mod helpers; use crate::editor::Editor; use crate::helpers::*; +use libc::STDIN_FILENO; +use std::io::Error; +use std::result::Result; -fn main() { +fn main() -> Result<(), Error> { // Save original terminal flags let original_termios = get_termios(STDIN_FILENO); @@ -18,7 +21,7 @@ fn main() { // `None` is returned on a quit action, in other cases, `Some(())` is returned, // continuing the loop loop { - editor.refresh_screen(); + editor.refresh_screen()?; if editor.process_keypress().is_none() { break; @@ -27,4 +30,6 @@ fn main() { // Restore previous terminal flags before exit disable_raw_mode(&original_termios); + + Ok(()) }