From c1c87429fbdb7a0046a41bdf473e7069f1ecc184 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 27 Aug 2019 17:38:05 -0400 Subject: [PATCH] Vertical scrolling, and improved escape code matching --- src/editor.rs | 183 +++++++++++++++++++++++++++++++------------------ src/helpers.rs | 25 +++---- src/main.rs | 10 ++- 3 files changed, 132 insertions(+), 86 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index 6f03b92..f7f175b 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -38,7 +38,7 @@ pub struct Editor { /// Keycode mapping enum #[derive(Copy, Clone, Debug, PartialEq)] -enum EditorKey { +pub enum EditorKey { Escape, ArrowLeft, ArrowRight, @@ -49,6 +49,8 @@ enum EditorKey { EndKey, PageUp, PageDown, + /// Function keys (F1, etc.) T holds the index + Function(T), /// Any other type of character OtherKey(T), } @@ -83,71 +85,104 @@ impl Editor { // Terminal // ------------------------------------------------------------------------ - fn read_key(&mut self) -> Option>> { - // TODO: see if return type can be simplified + 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 buffer = BufReader::with_capacity(4, stdin); + buffer.read_to_string(&mut in_str).unwrap(); - let mut output: Vec> = vec![]; + let mut input: Vec> = vec![]; for char in in_str.chars() { - output.push(match char { + input.push(match char { '\x1b' => Escape, _ => OtherKey(char), }); } - if output.len() == 0 { + if input.is_empty() { 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, - }; + if input[0].eq(&Escape) { + match input.len() { + 5 => { + // Escape code of the form `^[[NM~` + if input[4].eq(&OtherKey('~')) { + let action = match (input[2].unwrap(), input[3].unwrap()) { + ('1', '5') => Function('5'), + ('1', '7') => Function('6'), + ('1', '8') => Function('7'), + ('1', '9') => Function('8'), + ('2', '0') => Function('9'), + ('2', '1') => Function('X'), // F10 + ('2', '4') => Function('T'), // F12 + _ => Escape, + }; - return Some(vec![action]); + return Some(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, + 4 => { + // Escape code of the form `^[[N~` + if input[3].eq(&OtherKey('~')) { + let action = match input[2].unwrap() { + '1' => HomeKey, + '3' => DeleteKey, + '4' => EndKey, + '5' => PageUp, + '6' => PageDown, + '7' => HomeKey, + '8' => EndKey, + _ => input[2]// Escape, + }; - // Eh, just return escape otherwise - _ => return Some(vec![Escape]), - }; + return Some(action); + } + }, + 3 => { + match input[1] { + // Escape code of the form `^[[X` + OtherKey('[') => { + let action = match input[2].unwrap() { + 'A' => ArrowUp, + 'B' => ArrowDown, + 'C' => ArrowRight, + 'D' => ArrowLeft, + 'H' => HomeKey, + 'F' => EndKey, - return Some(vec![action]); - } - if output[1].eq(&OtherKey('O')) { - let action = match output[2] { - OtherKey('H') => HomeKey, - OtherKey('F') => EndKey, - _ => Escape, - }; + // Eh, just return escape otherwise + _ => input[2] //Escape, + }; - return Some(vec![action]); + return Some(action); + }, + // Escape code of the form `^[OX` + OtherKey('O') => { + let action = match input[2].unwrap() { + 'H' => HomeKey, + 'F' => EndKey, + 'P' => Function('1'), + 'Q' => Function('2'), + 'R' => Function('3'), + 'S' => Function('4'), + _ => input[2] //Escape, + }; + + return Some(action); + }, + _ => return Some(input[1]) + } + }, + _ => return Some(input[0]), } } - return Some(output); + // If the character doesn't match any escape sequences, just + // pass that character on + return Some(input[0]); } fn get_window_size(&mut self) -> TermSize { @@ -164,22 +199,22 @@ impl Editor { fn move_cursor(&mut self, key: &EditorKey) { match key { ArrowLeft => { - if self.cursor_x != 0 { + if self.cursor_x > 0 { self.cursor_x -= 1; } } ArrowRight => { - if self.cursor_x != self.screen_cols - 1 { + if self.cursor_x < self.screen_cols - 1 { self.cursor_x += 1; } } ArrowUp => { - if self.cursor_y != 0 { + if self.cursor_y > 0 { self.cursor_y -= 1; } } ArrowDown => { - if self.cursor_y != self.screen_rows - 1 { + if self.cursor_y < self.rows.len() { self.cursor_y += 1; } } @@ -188,39 +223,41 @@ impl Editor { } /// Route user input to the appropriate handler method - pub fn process_keypress(&mut self) -> Option<()> { - let chars = self.read_key(); - if chars.is_some() { - let chars = chars.unwrap(); - let first = &chars[0]; - - match first { + pub fn process_keypress(&mut self) -> Option> { + let key = self.read_key(); + if key.is_some() { + let char = key.unwrap(); + match char { OtherKey(c) => { - if c == &ctrl_key('q') { + if c == ctrl_key('q') { print!("\x1b[2J"); print!("\x1b[H"); // Break out of the input loop return None; } } + DeleteKey => (), + Escape => (), + ArrowUp => self.move_cursor(&ArrowUp), + ArrowDown => self.move_cursor(&ArrowDown), + ArrowLeft => self.move_cursor(&ArrowLeft), + ArrowRight => self.move_cursor(&ArrowRight), + PageUp => self.page_up_or_down(PageUp), + PageDown => self.page_up_or_down(PageDown), 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), + }, _ => (), }; + + return key } // Continue the main input loop - Some(()) + Some(OtherKey('\0')) } fn page_up_or_down(&mut self, key: EditorKey) { @@ -248,9 +285,20 @@ impl Editor { self.output_buffer.push_str(str); } + fn scroll(&mut self) { + if self.cursor_y < self.row_offset { + 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 draw_rows(&mut self) { for y in 0..self.screen_rows { - if y >= self.rows.len() { + let file_row = y + self.row_offset; + if file_row >= self.rows.len() { if self.rows.is_empty() && y == (self.screen_rows / 3) { let mut welcome = format!( "Oxidized Kilo editor -- version {}", @@ -275,7 +323,7 @@ impl Editor { self.append_out("~"); } } else { - let mut output = self.rows[y].chars.clone(); + let mut output = self.rows[file_row].chars.clone(); if output.len() > self.screen_cols { output.truncate(self.screen_cols); } @@ -290,6 +338,7 @@ impl Editor { } pub fn refresh_screen(&mut self) -> io::Result<()> { + self.scroll(); self.output_buffer.clear(); // Hide cursor, reposition cursor @@ -299,7 +348,7 @@ impl Editor { self.draw_rows(); // Move cursor to state position - let cursor_code = format!("\x1b[{};{}H", self.cursor_y + 1, self.cursor_x + 1); + let cursor_code = format!("\x1b[{y};{x}H", y=self.cursor_y + 1, x=self.cursor_x + 1); self.append_out(&cursor_code); // Show cursor diff --git a/src/helpers.rs b/src/helpers.rs index bbc3ed2..40b7aa4 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -51,31 +51,21 @@ pub fn enable_raw_mode() { raw.input_flags.remove( InputFlags::BRKINT - | InputFlags::IGNCR - | InputFlags::IGNBRK | InputFlags::ICRNL - | InputFlags::INLCR | InputFlags::INPCK | InputFlags::ISTRIP - | InputFlags::IXON - | InputFlags::PARMRK, + | InputFlags::IXON, ); raw.output_flags.remove(OutputFlags::OPOST); - raw.local_flags.remove( - LocalFlags::ECHO - | LocalFlags::ECHONL - | LocalFlags::ICANON - | LocalFlags::IEXTEN - | LocalFlags::ISIG, - ); - - raw.control_flags - .remove(ControlFlags::CSIZE | ControlFlags::PARENB); - // 8 bit characters - raw.control_flags |= termios::ControlFlags::CS8; + raw.control_flags |= ControlFlags::CS8; + + raw.local_flags + .remove(LocalFlags::ECHO | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG); + + // raw.control_flags.remove(ControlFlags::CSIZE | ControlFlags::PARENB); raw.control_chars[SpecialCharacterIndices::VMIN as usize] = 0; raw.control_chars[SpecialCharacterIndices::VTIME as usize] = 1; @@ -89,6 +79,7 @@ pub fn disable_raw_mode(original: &Termios) { termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, original).unwrap(); } +/// Attempt to get the size of the terminal (in rows and columns) from an `ioctl` call pub fn get_term_size() -> Option { let raw = UnixTermSize { rows: 0, diff --git a/src/main.rs b/src/main.rs index 878b86e..364f83d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ mod editor; mod helpers; -use crate::editor::Editor; +use crate::editor::{Editor, EditorKey}; use crate::helpers::*; use libc::STDIN_FILENO; use std::env; @@ -31,9 +31,15 @@ fn main() -> Result<(), Error> { loop { editor.refresh_screen()?; - if editor.process_keypress().is_none() { + let key = editor.process_keypress(); + if key.is_none() { break; } + + /*match key.unwrap() { + EditorKey::OtherKey('\0') => (), + _ => println!("{:?}\r\n", key) + } */ } // Restore previous terminal flags before exit