2019-08-22 14:25:18 -04:00
|
|
|
//! Editor functionality
|
2019-08-22 16:44:47 -04:00
|
|
|
use crate::helpers::*;
|
2019-08-22 14:25:18 -04:00
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
use std::cmp::PartialEq;
|
2019-08-27 12:22:19 -04:00
|
|
|
use std::fs::File;
|
2019-08-22 16:44:47 -04:00
|
|
|
use std::io;
|
|
|
|
use std::io::prelude::*;
|
2019-08-23 16:46:04 -04:00
|
|
|
use std::io::BufReader;
|
2019-08-22 16:44:47 -04:00
|
|
|
|
2019-08-27 12:22:19 -04:00
|
|
|
use self::EditorKey::*;
|
|
|
|
|
2019-08-27 15:13:32 -04:00
|
|
|
/// A representation of a line in the editor
|
2019-08-27 12:22:19 -04:00
|
|
|
#[derive(Debug, Default)]
|
2019-08-27 08:30:51 -04:00
|
|
|
pub struct EditorRow {
|
2019-08-27 12:22:19 -04:00
|
|
|
chars: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EditorRow {
|
|
|
|
pub fn new(str: &str) -> Self {
|
|
|
|
EditorRow {
|
|
|
|
chars: str.to_owned(),
|
|
|
|
}
|
|
|
|
}
|
2019-08-27 08:30:51 -04:00
|
|
|
}
|
|
|
|
|
2019-08-22 16:44:47 -04:00
|
|
|
/// Main structure for the editor
|
2019-08-27 15:13:32 -04:00
|
|
|
/// `EditorConfig` struct in C version
|
2019-08-23 16:46:04 -04:00
|
|
|
#[derive(Debug, Default)]
|
|
|
|
pub struct Editor {
|
2019-08-26 16:39:52 -04:00
|
|
|
cursor_x: usize,
|
|
|
|
cursor_y: usize,
|
2019-08-27 15:13:32 -04:00
|
|
|
rows: Vec<EditorRow>,
|
|
|
|
row_offset: usize,
|
2019-08-26 10:04:12 -04:00
|
|
|
screen_cols: usize,
|
|
|
|
screen_rows: usize,
|
|
|
|
output_buffer: String,
|
2019-08-23 16:46:04 -04:00
|
|
|
}
|
2019-08-22 14:25:18 -04:00
|
|
|
|
2019-08-27 15:13:32 -04:00
|
|
|
/// Keycode mapping enum
|
2019-08-26 16:39:52 -04:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
|
|
enum EditorKey<T> {
|
|
|
|
Escape,
|
|
|
|
ArrowLeft,
|
|
|
|
ArrowRight,
|
|
|
|
ArrowUp,
|
|
|
|
ArrowDown,
|
|
|
|
DeleteKey,
|
|
|
|
HomeKey,
|
|
|
|
EndKey,
|
|
|
|
PageUp,
|
|
|
|
PageDown,
|
2019-08-27 15:13:32 -04:00
|
|
|
/// Any other type of character
|
2019-08-26 16:39:52 -04:00
|
|
|
OtherKey(T),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EditorKey<char> {
|
|
|
|
pub fn unwrap(self) -> char {
|
|
|
|
match self {
|
|
|
|
self::OtherKey(val) => val,
|
|
|
|
_ => panic!("called `EditorKey::unwrap()` on a `None` value"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-22 14:25:18 -04:00
|
|
|
impl Editor {
|
2019-08-27 15:13:32 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// Init
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2019-08-22 14:25:18 -04:00
|
|
|
pub fn new() -> Self {
|
2019-08-23 16:46:04 -04:00
|
|
|
let mut instance = Self::default();
|
|
|
|
let size = instance.get_window_size();
|
2019-08-27 15:13:32 -04:00
|
|
|
instance.rows = vec![];
|
2019-08-26 16:39:52 -04:00
|
|
|
instance.cursor_x = 0;
|
|
|
|
instance.cursor_y = 0;
|
2019-08-26 10:04:12 -04:00
|
|
|
instance.screen_cols = size.cols as usize;
|
|
|
|
instance.screen_rows = size.rows as usize;
|
2019-08-23 16:46:04 -04:00
|
|
|
|
|
|
|
instance
|
2019-08-22 16:44:47 -04:00
|
|
|
}
|
|
|
|
|
2019-08-27 15:13:32 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// Terminal
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
fn read_key(&mut self) -> Option<Vec<EditorKey<char>>> {
|
2019-08-27 15:13:32 -04:00
|
|
|
// TODO: see if return type can be simplified
|
2019-08-22 16:44:47 -04:00
|
|
|
let stdin = io::stdin();
|
2019-08-23 14:57:26 -04:00
|
|
|
let stdin = stdin.lock();
|
2019-08-22 16:44:47 -04:00
|
|
|
let mut in_str = String::new();
|
2019-08-23 14:57:26 -04:00
|
|
|
let mut input = BufReader::with_capacity(3, stdin);
|
2019-08-22 16:44:47 -04:00
|
|
|
input.read_to_string(&mut in_str).unwrap();
|
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
let mut output: Vec<EditorKey<char>> = vec![];
|
2019-08-23 13:34:00 -04:00
|
|
|
|
|
|
|
for char in in_str.chars() {
|
2019-08-26 16:39:52 -04:00
|
|
|
output.push(match char {
|
|
|
|
'\x1b' => Escape,
|
|
|
|
_ => OtherKey(char),
|
|
|
|
});
|
2019-08-23 13:34:00 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if output.len() == 0 {
|
2019-08-23 14:57:26 -04:00
|
|
|
return None;
|
2019-08-22 16:44:47 -04:00
|
|
|
}
|
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
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,
|
|
|
|
};
|
2019-08-23 16:46:04 -04:00
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
2019-08-23 16:46:04 -04:00
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
return Some(output);
|
2019-08-23 16:46:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_window_size(&mut self) -> TermSize {
|
|
|
|
match get_term_size() {
|
|
|
|
Some(size) => size,
|
2019-08-27 08:30:51 -04:00
|
|
|
None => unimplemented!("The easy way usually works"),
|
2019-08-23 16:46:04 -04:00
|
|
|
}
|
|
|
|
}
|
2019-08-22 16:44:47 -04:00
|
|
|
|
2019-08-27 15:13:32 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// Input
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
fn move_cursor(&mut self, key: &EditorKey<char>) {
|
|
|
|
match key {
|
|
|
|
ArrowLeft => {
|
|
|
|
if self.cursor_x != 0 {
|
|
|
|
self.cursor_x -= 1;
|
|
|
|
}
|
2019-08-27 08:30:51 -04:00
|
|
|
}
|
2019-08-26 16:39:52 -04:00
|
|
|
ArrowRight => {
|
|
|
|
if self.cursor_x != self.screen_cols - 1 {
|
|
|
|
self.cursor_x += 1;
|
|
|
|
}
|
2019-08-27 08:30:51 -04:00
|
|
|
}
|
2019-08-26 16:39:52 -04:00
|
|
|
ArrowUp => {
|
|
|
|
if self.cursor_y != 0 {
|
|
|
|
self.cursor_y -= 1;
|
|
|
|
}
|
2019-08-27 08:30:51 -04:00
|
|
|
}
|
2019-08-26 16:39:52 -04:00
|
|
|
ArrowDown => {
|
|
|
|
if self.cursor_y != self.screen_rows - 1 {
|
|
|
|
self.cursor_y += 1;
|
|
|
|
}
|
2019-08-27 08:30:51 -04:00
|
|
|
}
|
2019-08-26 16:39:52 -04:00
|
|
|
_ => (),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-08-23 16:46:04 -04:00
|
|
|
/// Route user input to the appropriate handler method
|
2019-08-23 13:34:00 -04:00
|
|
|
pub fn process_keypress(&mut self) -> Option<()> {
|
2019-08-26 16:39:52 -04:00
|
|
|
let chars = self.read_key();
|
2019-08-27 15:13:32 -04:00
|
|
|
if chars.is_some() {
|
2019-08-26 16:39:52 -04:00
|
|
|
let chars = chars.unwrap();
|
|
|
|
let first = &chars[0];
|
2019-08-22 16:44:47 -04:00
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
match first {
|
|
|
|
OtherKey(c) => {
|
|
|
|
if c == &ctrl_key('q') {
|
|
|
|
print!("\x1b[2J");
|
|
|
|
print!("\x1b[H");
|
|
|
|
// Break out of the input loop
|
|
|
|
return None;
|
|
|
|
}
|
2019-08-27 08:30:51 -04:00
|
|
|
}
|
2019-08-26 16:39:52 -04:00
|
|
|
HomeKey => {
|
|
|
|
self.cursor_x = 0;
|
2019-08-27 08:30:51 -04:00
|
|
|
}
|
2019-08-26 16:39:52 -04:00
|
|
|
EndKey => {
|
|
|
|
self.cursor_x = self.screen_cols - 1;
|
2019-08-27 08:30:51 -04:00
|
|
|
}
|
2019-08-26 16:39:52 -04:00
|
|
|
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),
|
|
|
|
_ => (),
|
|
|
|
};
|
|
|
|
}
|
2019-08-27 15:13:32 -04:00
|
|
|
|
|
|
|
// Continue the main input loop
|
|
|
|
Some(())
|
2019-08-26 16:39:52 -04:00
|
|
|
}
|
2019-08-23 13:34:00 -04:00
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
fn page_up_or_down(&mut self, key: EditorKey<char>) {
|
2019-08-27 08:30:51 -04:00
|
|
|
let mut times = self.screen_rows;
|
2019-08-23 13:34:00 -04:00
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
while times > 1 {
|
|
|
|
times -= 1;
|
|
|
|
match key {
|
|
|
|
PageUp => self.move_cursor(&ArrowUp),
|
|
|
|
PageDown => self.move_cursor(&ArrowDown),
|
|
|
|
_ => (),
|
2019-08-23 13:34:00 -04:00
|
|
|
}
|
|
|
|
}
|
2019-08-22 14:25:18 -04:00
|
|
|
}
|
2019-08-23 13:34:00 -04:00
|
|
|
|
2019-08-27 15:13:32 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// Output
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2019-08-26 10:04:12 -04:00
|
|
|
/// Equivalent of the abAppend function
|
|
|
|
/// in the original tutorial, just appends
|
|
|
|
/// to the `output_buffer` String in the
|
|
|
|
/// editor struct.
|
|
|
|
fn append_out(&mut self, str: &str) {
|
|
|
|
self.output_buffer.push_str(str);
|
|
|
|
}
|
|
|
|
|
2019-08-23 16:46:04 -04:00
|
|
|
fn draw_rows(&mut self) {
|
2019-08-26 10:04:12 -04:00
|
|
|
for y in 0..self.screen_rows {
|
2019-08-27 15:13:32 -04:00
|
|
|
if y >= self.rows.len() {
|
|
|
|
if self.rows.is_empty() && y == (self.screen_rows / 3) {
|
|
|
|
let mut welcome = format!(
|
|
|
|
"Oxidized Kilo editor -- version {}",
|
|
|
|
env!("CARGO_PKG_VERSION")
|
|
|
|
);
|
2019-08-27 12:22:19 -04:00
|
|
|
if welcome.len() > self.screen_cols {
|
|
|
|
welcome.truncate(self.screen_cols)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Center welcome message
|
|
|
|
let mut padding = (self.screen_cols - welcome.len()) / 2;
|
|
|
|
if padding > 0 {
|
|
|
|
self.append_out("~");
|
|
|
|
padding -= 1;
|
|
|
|
}
|
2019-08-27 15:13:32 -04:00
|
|
|
for _ in 0..padding {
|
2019-08-27 12:22:19 -04:00
|
|
|
self.append_out(" ");
|
|
|
|
}
|
2019-08-26 10:04:12 -04:00
|
|
|
|
2019-08-27 12:22:19 -04:00
|
|
|
self.append_out(&welcome);
|
|
|
|
} else {
|
2019-08-26 10:04:12 -04:00
|
|
|
self.append_out("~");
|
|
|
|
}
|
|
|
|
} else {
|
2019-08-27 15:13:32 -04:00
|
|
|
let mut output = self.rows[y].chars.clone();
|
2019-08-27 12:22:19 -04:00
|
|
|
if output.len() > self.screen_cols {
|
|
|
|
output.truncate(self.screen_cols);
|
|
|
|
}
|
|
|
|
self.append_out(&output);
|
2019-08-26 10:04:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
self.append_out("\x1b[K");
|
2019-08-26 16:39:52 -04:00
|
|
|
if y < (self.screen_rows - 1) {
|
2019-08-26 10:04:12 -04:00
|
|
|
self.append_out("\r\n");
|
|
|
|
}
|
2019-08-23 16:46:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
pub fn refresh_screen(&mut self) -> io::Result<()> {
|
2019-08-26 10:04:12 -04:00
|
|
|
self.output_buffer.clear();
|
2019-08-23 16:46:04 -04:00
|
|
|
|
2019-08-26 10:04:12 -04:00
|
|
|
// Hide cursor, reposition cursor
|
2019-08-27 15:13:32 -04:00
|
|
|
self.append_out("\x1b[?25l");
|
|
|
|
self.append_out("\x1b[H");
|
2019-08-23 16:46:04 -04:00
|
|
|
|
|
|
|
self.draw_rows();
|
2019-08-23 13:34:00 -04:00
|
|
|
|
2019-08-26 16:39:52 -04:00
|
|
|
// 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
|
2019-08-26 10:04:12 -04:00
|
|
|
self.append_out("\x1b[?25h");
|
|
|
|
|
|
|
|
let stdout = io::stdout();
|
|
|
|
let mut handle = stdout.lock();
|
2019-08-26 16:39:52 -04:00
|
|
|
handle.write_all(&self.output_buffer.as_bytes())
|
2019-08-23 14:57:26 -04:00
|
|
|
}
|
2019-08-27 08:30:51 -04:00
|
|
|
|
2019-08-27 15:13:32 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// Row Operations
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
|
|
|
fn append_row(&mut self, row: &str) {
|
|
|
|
self.rows.push(EditorRow::new(row));
|
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// File I/O
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/// Open a file for display
|
2019-08-27 12:22:19 -04:00
|
|
|
pub fn open(&mut self, filename: &str) -> io::Result<()> {
|
|
|
|
let file = File::open(filename)?;
|
|
|
|
let buf_reader = BufReader::new(file);
|
|
|
|
|
2019-08-27 15:13:32 -04:00
|
|
|
let lines = buf_reader.lines().map(|l| l.unwrap());
|
2019-08-27 12:22:19 -04:00
|
|
|
|
2019-08-27 15:13:32 -04:00
|
|
|
for line in lines {
|
|
|
|
self.append_row(&line);
|
|
|
|
}
|
2019-08-27 12:22:19 -04:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2019-08-27 08:30:51 -04:00
|
|
|
}
|