Rough progress commit, chapter 3 with cursor issues

This commit is contained in:
Timothy Warren 2019-08-26 16:39:52 -04:00
parent 208b92b713
commit 761e55236b
3 changed files with 169 additions and 85 deletions

View File

@ -1,6 +1,7 @@
//! Editor functionality //! Editor functionality
use crate::helpers::*; use crate::helpers::*;
use std::cmp::PartialEq;
use std::io; use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use std::io::BufReader; use std::io::BufReader;
@ -10,16 +11,46 @@ use std::io::BufReader;
/// impl blocks are split similarly to the original C implementation /// impl blocks are split similarly to the original C implementation
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Editor { pub struct Editor {
cursor_x: usize,
cursor_y: usize,
screen_cols: usize, screen_cols: usize,
screen_rows: usize, screen_rows: usize,
output_buffer: String, output_buffer: String,
} }
#[derive(Copy, Clone, Debug, PartialEq)]
enum EditorKey<T> {
Escape,
ArrowLeft,
ArrowRight,
ArrowUp,
ArrowDown,
DeleteKey,
HomeKey,
EndKey,
PageUp,
PageDown,
OtherKey(T),
}
impl EditorKey<char> {
pub fn unwrap(self) -> char {
match self {
self::OtherKey(val) => val,
_ => panic!("called `EditorKey::unwrap()` on a `None` value"),
}
}
}
use self::EditorKey::*;
// init // init
impl Editor { impl Editor {
pub fn new() -> Self { pub fn new() -> Self {
let mut instance = Self::default(); let mut instance = Self::default();
let size = instance.get_window_size(); let size = instance.get_window_size();
instance.cursor_x = 0;
instance.cursor_y = 0;
instance.screen_cols = size.cols as usize; instance.screen_cols = size.cols as usize;
instance.screen_rows = size.rows as usize; instance.screen_rows = size.rows as usize;
@ -29,78 +60,158 @@ impl Editor {
// Terminal // Terminal
impl Editor { impl Editor {
fn read_key(&mut self) -> Option<Vec<char>> { fn read_key(&mut self) -> Option<Vec<EditorKey<char>>> {
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 input = BufReader::with_capacity(3, stdin);
input.read_to_string(&mut in_str).unwrap(); input.read_to_string(&mut in_str).unwrap();
let mut output: Vec<char> = vec![]; let mut output: Vec<EditorKey<char>> = vec![];
for char in in_str.chars() { for char in in_str.chars() {
output.push(char); output.push(match char {
'\x1b' => Escape,
_ => OtherKey(char),
});
} }
if output.len() == 0 { if output.len() == 0 {
return None; 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); 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 { fn get_window_size(&mut self) -> TermSize {
match get_term_size() { match get_term_size() {
Some(size) => size, Some(size) => size,
None => { None => unimplemented!("The easy way usually works")
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 // Input
impl Editor { impl Editor {
fn move_cursor(&mut self, key: &EditorKey<char>) {
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 /// Route user input to the appropriate handler method
pub fn process_keypress(&mut self) -> Option<()> { pub fn process_keypress(&mut self) -> Option<()> {
match self.read_key() { let chars = self.read_key();
// Just continue the input loop on an "empty" keypress if chars.is_none() {
None => Some(()), Some(()) // Continue input loop on empty input
Some(chars) => { } else {
let first = chars[0]; let chars = chars.unwrap();
// print!("{:?}\r\n", &chars);
let first = &chars[0];
if first == ctrl_key('q') { match first {
clear_and_reset(); 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 // Continue the main input loop
return None; Some(())
} }
}
print!("{:?}\r\n", chars); fn page_up_or_down(&mut self, key: EditorKey<char>) {
let mut times = self.screen_rows;
// Continue the main input loop while times > 1 {
Some(()) 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("~");
} }
self.append_out("\x1b[K"); self.append_out("\x1b[K");
if y < (self.screen_rows as usize - 1) { if y < (self.screen_rows - 1) {
self.append_out("\r\n"); self.append_out("\r\n");
} }
} }
} }
pub fn refresh_screen(&mut self) { pub fn refresh_screen(&mut self) -> io::Result<()> {
self.output_buffer.clear(); self.output_buffer.clear();
// Hide cursor, reposition cursor // Hide cursor, reposition cursor
self.append_out("\x1b[?25l"); //self.append_out("\x1b[?25l");
self.append_out("\x1b[H"); //self.append_out("\x1b[H");
self.draw_rows(); 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"); self.append_out("\x1b[?25h");
let stdout = io::stdout(); let stdout = io::stdout();
let mut handle = stdout.lock(); let mut handle = stdout.lock();
handle.write_all(&self.output_buffer.as_bytes()).unwrap(); handle.write_all(&self.output_buffer.as_bytes())
} }
} }

View File

@ -1,17 +1,10 @@
use libc::ioctl; use libc::ioctl;
/// Helper functions, especially to reproduce C std/posix functions use libc::{c_ushort, STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ};
use libc::{c_ushort, STDOUT_FILENO, TIOCGWINSZ};
use nix::errno::*;
use nix::sys::termios; use nix::sys::termios;
use nix::sys::termios::{ use nix::sys::termios::{
ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios, ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios,
}; };
use std::io;
use std::io::prelude::*;
use std::os::unix::io::RawFd; use std::os::unix::io::RawFd;
use std::process::exit;
#[derive(Debug)] #[derive(Debug)]
pub struct TermSize { pub struct TermSize {
@ -32,13 +25,6 @@ struct UnixTermSize {
y: c_ushort, 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 /// Convert Ctrl+letter chords to their
/// ASCII table equivalents /// ASCII table equivalents
#[inline] #[inline]
@ -54,14 +40,6 @@ pub fn ctrl_key(c: char) -> char {
(key & 0x1f) as 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 /// Get a `Termios` struct, for getting/setting terminal flags
pub fn get_termios(fd: RawFd) -> Termios { pub fn get_termios(fd: RawFd) -> Termios {
termios::tcgetattr(fd).unwrap() termios::tcgetattr(fd).unwrap()
@ -111,19 +89,6 @@ 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();
} }
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<TermSize> { pub fn get_term_size() -> Option<TermSize> {
let raw = UnixTermSize { let raw = UnixTermSize {
rows: 0, rows: 0,

View File

@ -3,8 +3,11 @@ mod helpers;
use crate::editor::Editor; use crate::editor::Editor;
use crate::helpers::*; 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 // Save original terminal flags
let original_termios = get_termios(STDIN_FILENO); 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, // `None` is returned on a quit action, in other cases, `Some(())` is returned,
// continuing the loop // continuing the loop
loop { loop {
editor.refresh_screen(); editor.refresh_screen()?;
if editor.process_keypress().is_none() { if editor.process_keypress().is_none() {
break; break;
@ -27,4 +30,6 @@ fn main() {
// Restore previous terminal flags before exit // Restore previous terminal flags before exit
disable_raw_mode(&original_termios); disable_raw_mode(&original_termios);
Ok(())
} }