Rough progress commit, chapter 3 with cursor issues
This commit is contained in:
parent
208b92b713
commit
761e55236b
196
src/editor.rs
196
src/editor.rs
@ -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,80 +60,160 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Some(output);
|
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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
return Some(output);
|
||||||
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);
|
||||||
if first == ctrl_key('q') {
|
let first = &chars[0];
|
||||||
clear_and_reset();
|
|
||||||
|
|
||||||
|
match first {
|
||||||
|
OtherKey(c) => {
|
||||||
|
if c == &ctrl_key('q') {
|
||||||
|
print!("\x1b[2J");
|
||||||
|
print!("\x1b[H");
|
||||||
// Break out of the input loop
|
// Break out of the input loop
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
print!("{:?}\r\n", chars);
|
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),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
// Continue the main input loop
|
// Continue the main input loop
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn page_up_or_down(&mut self, key: EditorKey<char>) {
|
||||||
|
let mut times = self.screen_rows;
|
||||||
|
|
||||||
|
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("~");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user