Vertical scrolling, and improved escape code matching
This commit is contained in:
parent
160b840151
commit
c1c87429fb
183
src/editor.rs
183
src/editor.rs
@ -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
|
||||||
|
@ -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,
|
||||||
|
10
src/main.rs
10
src/main.rs
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user