Vertical scrolling, and improved escape code matching

This commit is contained in:
Timothy Warren 2019-08-27 17:38:05 -04:00
parent 160b840151
commit c1c87429fb
3 changed files with 132 additions and 86 deletions

View File

@ -38,7 +38,7 @@ pub struct Editor {
/// Keycode mapping enum /// Keycode mapping enum
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
enum EditorKey<T> { pub enum EditorKey<T> {
Escape, Escape,
ArrowLeft, ArrowLeft,
ArrowRight, ArrowRight,
@ -49,6 +49,8 @@ enum EditorKey<T> {
EndKey, EndKey,
PageUp, PageUp,
PageDown, PageDown,
/// Function keys (F1, etc.) T holds the index
Function(T),
/// Any other type of character /// Any other type of character
OtherKey(T), OtherKey(T),
} }
@ -83,71 +85,104 @@ impl Editor {
// Terminal // Terminal
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
fn read_key(&mut self) -> Option<Vec<EditorKey<char>>> { fn read_key(&mut self) -> Option<EditorKey<char>> {
// TODO: see if return type can be simplified
let stdin = io::stdin(); let stdin = io::stdin();
let stdin = stdin.lock(); let stdin = stdin.lock();
let mut in_str = String::new(); let mut in_str = String::new();
let mut input = BufReader::with_capacity(3, stdin); let mut buffer = BufReader::with_capacity(4, stdin);
input.read_to_string(&mut in_str).unwrap(); buffer.read_to_string(&mut in_str).unwrap();
let mut output: Vec<EditorKey<char>> = vec![]; let mut input: Vec<EditorKey<char>> = vec![];
for char in in_str.chars() { for char in in_str.chars() {
output.push(match char { input.push(match char {
'\x1b' => Escape, '\x1b' => Escape,
_ => OtherKey(char), _ => OtherKey(char),
}); });
} }
if output.len() == 0 { if input.is_empty() {
return None; return None;
} }
if output[0].eq(&Escape) { if input[0].eq(&Escape) {
if output.len() == 4 { match input.len() {
if output[3].eq(&OtherKey('~')) { 5 => {
let action = match output[2].unwrap() { // Escape code of the form `^[[NM~`
'1' => HomeKey, if input[4].eq(&OtherKey('~')) {
'3' => DeleteKey, let action = match (input[2].unwrap(), input[3].unwrap()) {
'4' => EndKey, ('1', '5') => Function('5'),
'5' => PageUp, ('1', '7') => Function('6'),
'6' => PageDown, ('1', '8') => Function('7'),
'7' => HomeKey, ('1', '9') => Function('8'),
'8' => EndKey, ('2', '0') => Function('9'),
_ => Escape, ('2', '1') => Function('X'), // F10
}; ('2', '4') => Function('T'), // F12
_ => Escape,
};
return Some(vec![action]); return Some(action);
}
} }
} 4 => {
if output[1].eq(&OtherKey('[')) { // Escape code of the form `^[[N~`
let action = match output[2] { if input[3].eq(&OtherKey('~')) {
OtherKey('A') => ArrowUp, let action = match input[2].unwrap() {
OtherKey('B') => ArrowDown, '1' => HomeKey,
OtherKey('C') => ArrowRight, '3' => DeleteKey,
OtherKey('D') => ArrowLeft, '4' => EndKey,
OtherKey('H') => HomeKey, '5' => PageUp,
OtherKey('F') => EndKey, '6' => PageDown,
'7' => HomeKey,
'8' => EndKey,
_ => input[2]// Escape,
};
// Eh, just return escape otherwise return Some(action);
_ => return Some(vec![Escape]), }
}; },
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]); // Eh, just return escape otherwise
} _ => input[2] //Escape,
if output[1].eq(&OtherKey('O')) { };
let action = match output[2] {
OtherKey('H') => HomeKey,
OtherKey('F') => EndKey,
_ => 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 { fn get_window_size(&mut self) -> TermSize {
@ -164,22 +199,22 @@ impl Editor {
fn move_cursor(&mut self, key: &EditorKey<char>) { fn move_cursor(&mut self, key: &EditorKey<char>) {
match key { match key {
ArrowLeft => { ArrowLeft => {
if self.cursor_x != 0 { if self.cursor_x > 0 {
self.cursor_x -= 1; self.cursor_x -= 1;
} }
} }
ArrowRight => { ArrowRight => {
if self.cursor_x != self.screen_cols - 1 { if self.cursor_x < self.screen_cols - 1 {
self.cursor_x += 1; self.cursor_x += 1;
} }
} }
ArrowUp => { ArrowUp => {
if self.cursor_y != 0 { if self.cursor_y > 0 {
self.cursor_y -= 1; self.cursor_y -= 1;
} }
} }
ArrowDown => { ArrowDown => {
if self.cursor_y != self.screen_rows - 1 { if self.cursor_y < self.rows.len() {
self.cursor_y += 1; self.cursor_y += 1;
} }
} }
@ -188,39 +223,41 @@ impl Editor {
} }
/// Route user input to the appropriate handler method /// Route user input to the appropriate handler method
pub fn process_keypress(&mut self) -> Option<()> { pub fn process_keypress(&mut self) -> Option<EditorKey<char>> {
let chars = self.read_key(); let key = self.read_key();
if chars.is_some() { if key.is_some() {
let chars = chars.unwrap(); let char = key.unwrap();
let first = &chars[0]; match char {
match first {
OtherKey(c) => { OtherKey(c) => {
if c == &ctrl_key('q') { if c == ctrl_key('q') {
print!("\x1b[2J"); print!("\x1b[2J");
print!("\x1b[H"); print!("\x1b[H");
// Break out of the input loop // Break out of the input loop
return None; 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 => { HomeKey => {
self.cursor_x = 0; self.cursor_x = 0;
} }
EndKey => { EndKey => {
self.cursor_x = self.screen_cols - 1; 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 // Continue the main input loop
Some(()) Some(OtherKey('\0'))
} }
fn page_up_or_down(&mut self, key: EditorKey<char>) { fn page_up_or_down(&mut self, key: EditorKey<char>) {
@ -248,9 +285,20 @@ impl Editor {
self.output_buffer.push_str(str); 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) { fn draw_rows(&mut self) {
for y in 0..self.screen_rows { 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) { if self.rows.is_empty() && y == (self.screen_rows / 3) {
let mut welcome = format!( let mut welcome = format!(
"Oxidized Kilo editor -- version {}", "Oxidized Kilo editor -- version {}",
@ -275,7 +323,7 @@ impl Editor {
self.append_out("~"); self.append_out("~");
} }
} else { } else {
let mut output = self.rows[y].chars.clone(); let mut output = self.rows[file_row].chars.clone();
if output.len() > self.screen_cols { if output.len() > self.screen_cols {
output.truncate(self.screen_cols); output.truncate(self.screen_cols);
} }
@ -290,6 +338,7 @@ impl Editor {
} }
pub fn refresh_screen(&mut self) -> io::Result<()> { pub fn refresh_screen(&mut self) -> io::Result<()> {
self.scroll();
self.output_buffer.clear(); self.output_buffer.clear();
// Hide cursor, reposition cursor // Hide cursor, reposition cursor
@ -299,7 +348,7 @@ impl Editor {
self.draw_rows(); self.draw_rows();
// Move cursor to state position // 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); self.append_out(&cursor_code);
// Show cursor // Show cursor

View File

@ -51,31 +51,21 @@ pub fn enable_raw_mode() {
raw.input_flags.remove( raw.input_flags.remove(
InputFlags::BRKINT InputFlags::BRKINT
| InputFlags::IGNCR
| InputFlags::IGNBRK
| InputFlags::ICRNL | InputFlags::ICRNL
| InputFlags::INLCR
| InputFlags::INPCK | InputFlags::INPCK
| InputFlags::ISTRIP | InputFlags::ISTRIP
| InputFlags::IXON | InputFlags::IXON,
| InputFlags::PARMRK,
); );
raw.output_flags.remove(OutputFlags::OPOST); 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 // 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::VMIN as usize] = 0;
raw.control_chars[SpecialCharacterIndices::VTIME as usize] = 1; 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(); 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<TermSize> { pub fn get_term_size() -> Option<TermSize> {
let raw = UnixTermSize { let raw = UnixTermSize {
rows: 0, rows: 0,

View File

@ -1,7 +1,7 @@
mod editor; mod editor;
mod helpers; mod helpers;
use crate::editor::Editor; use crate::editor::{Editor, EditorKey};
use crate::helpers::*; use crate::helpers::*;
use libc::STDIN_FILENO; use libc::STDIN_FILENO;
use std::env; use std::env;
@ -31,9 +31,15 @@ fn main() -> Result<(), Error> {
loop { loop {
editor.refresh_screen()?; editor.refresh_screen()?;
if editor.process_keypress().is_none() { let key = editor.process_keypress();
if key.is_none() {
break; break;
} }
/*match key.unwrap() {
EditorKey::OtherKey('\0') => (),
_ => println!("{:?}\r\n", key)
} */
} }
// Restore previous terminal flags before exit // Restore previous terminal flags before exit