rs-kilo/src/editor.rs

509 lines
16 KiB
Rust
Raw Normal View History

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
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-28 16:35:48 -04:00
use std::string::ToString;
2019-08-22 16:44:47 -04:00
2019-08-27 12:22:19 -04:00
use self::EditorKey::*;
2019-08-28 16:35:48 -04:00
const KILO_TAB_STOP: usize = 4;
/// 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,
2019-08-28 16:35:48 -04:00
render: String,
2019-08-27 12:22:19 -04:00
}
impl EditorRow {
2019-08-28 16:35:48 -04:00
pub fn new(chars: &str) -> Self {
let mut instance = EditorRow::default();
instance.chars = chars.to_owned();
instance
2019-08-27 12:22:19 -04:00
}
2019-08-27 08:30:51 -04:00
}
2019-08-22 16:44:47 -04:00
/// Main structure for the editor
/// `EditorConfig` struct in C version
2019-08-23 16:46:04 -04:00
#[derive(Debug, Default)]
pub struct Editor {
cursor_x: usize,
cursor_y: usize,
2019-08-28 16:35:48 -04:00
render_x: usize,
col_offset: usize,
row_offset: usize,
2019-08-26 10:04:12 -04:00
screen_cols: usize,
screen_rows: usize,
2019-08-28 16:35:48 -04:00
rows: Vec<EditorRow>,
filename: String,
2019-08-26 10:04:12 -04:00
output_buffer: String,
2019-08-23 16:46:04 -04:00
}
2019-08-22 14:25:18 -04:00
/// Keycode mapping enum
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum EditorKey<T> {
Escape,
ArrowLeft,
ArrowRight,
ArrowUp,
ArrowDown,
DeleteKey,
HomeKey,
EndKey,
PageUp,
PageDown,
/// Function keys (F1, etc.) T holds the index
Function(T),
/// Any other type of character
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 {
// ------------------------------------------------------------------------
// 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();
instance.rows = vec![];
instance.cursor_x = 0;
instance.cursor_y = 0;
2019-08-26 10:04:12 -04:00
instance.screen_cols = size.cols as usize;
2019-08-28 16:35:48 -04:00
instance.screen_rows = (size.rows - 1) as usize;
2019-08-23 16:46:04 -04:00
instance
2019-08-22 16:44:47 -04:00
}
// ------------------------------------------------------------------------
// Terminal
// ------------------------------------------------------------------------
fn read_key(&mut self) -> Option<EditorKey<char>> {
2019-08-22 16:44:47 -04:00
let stdin = io::stdin();
let stdin = stdin.lock();
2019-08-22 16:44:47 -04:00
let mut in_str = String::new();
let mut buffer = BufReader::with_capacity(4, stdin);
buffer.read_to_string(&mut in_str).unwrap();
2019-08-22 16:44:47 -04:00
let mut input: Vec<EditorKey<char>> = vec![];
for char in in_str.chars() {
input.push(match char {
'\x1b' => Escape,
_ => OtherKey(char),
});
}
if input.is_empty() {
return None;
2019-08-22 16:44:47 -04:00
}
if input[0].eq(&Escape) {
match input.len() {
5 => {
// Escape code of the form `^[[NM~`
if input[4].eq(&OtherKey('~')) {
let action = match (input[2].unwrap(), input[3].unwrap()) {
('1', '5') => Function('5'),
('1', '7') => Function('6'),
('1', '8') => Function('7'),
('1', '9') => Function('8'),
('2', '0') => Function('9'),
('2', '1') => Function('X'), // F10
('2', '4') => Function('T'), // F12
_ => Escape,
};
return Some(action);
}
}
4 => {
// Escape code of the form `^[[N~`
if input[3].eq(&OtherKey('~')) {
let action = match input[2].unwrap() {
'1' => HomeKey,
'3' => DeleteKey,
'4' => EndKey,
'5' => PageUp,
'6' => PageDown,
'7' => HomeKey,
'8' => EndKey,
2019-08-28 16:35:48 -04:00
_ => input[2], // Escape,
};
return Some(action);
}
2019-08-28 16:35:48 -04:00
}
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,
// Eh, just return escape otherwise
2019-08-28 16:35:48 -04:00
_ => input[2], //Escape,
};
return Some(action);
2019-08-28 16:35:48 -04:00
}
// 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'),
2019-08-28 16:35:48 -04:00
_ => input[2], //Escape,
};
return Some(action);
2019-08-28 16:35:48 -04:00
}
_ => return Some(input[1]),
}
2019-08-28 16:35:48 -04:00
}
_ => return Some(input[0]),
}
}
2019-08-23 16:46:04 -04:00
// If the character doesn't match any escape sequences, just
// pass that character on
return Some(input[0]);
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
// ------------------------------------------------------------------------
// Input
// ------------------------------------------------------------------------
fn move_cursor(&mut self, key: &EditorKey<char>) {
2019-08-28 16:35:48 -04:00
let row = self.rows.get(self.cursor_y);
match key {
ArrowLeft => {
2019-08-28 16:35:48 -04:00
if self.cursor_x != 0 {
// Move cursor left
self.cursor_x -= 1;
2019-08-28 16:35:48 -04:00
} else if self.cursor_y > 0 {
// Move to the end of the previous line
self.cursor_y -= 1;
self.cursor_x = self.rows[self.cursor_y].chars.len();
}
2019-08-27 08:30:51 -04:00
}
ArrowRight => {
2019-08-28 16:35:48 -04:00
if row.is_some() && self.cursor_x < row.unwrap().chars.len() {
// Move cursor right
self.cursor_x += 1;
2019-08-28 16:35:48 -04:00
} else if row.is_some() && self.cursor_x == row.unwrap().chars.len() {
// Move to start of next line
self.cursor_y += 1;
self.cursor_x = 0;
}
2019-08-27 08:30:51 -04:00
}
ArrowUp => {
if self.cursor_y > 0 {
self.cursor_y -= 1;
}
2019-08-27 08:30:51 -04:00
}
ArrowDown => {
if self.cursor_y < self.rows.len() {
self.cursor_y += 1;
}
2019-08-27 08:30:51 -04:00
}
_ => (),
};
2019-08-28 16:35:48 -04:00
let row = self.rows.get(self.cursor_y);
let row_len = if row.is_some() {
row.unwrap().chars.len()
} else {
0
};
// Snap to end of line when scrolling down
if self.cursor_x > row_len {
self.cursor_x = row_len;
}
}
2019-08-23 16:46:04 -04:00
/// Route user input to the appropriate handler method
pub fn process_keypress(&mut self) -> Option<EditorKey<char>> {
let key = self.read_key();
if key.is_some() {
let char = key.unwrap();
match char {
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
}
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 => {
self.cursor_x = 0;
2019-08-27 08:30:51 -04:00
}
EndKey => {
2019-08-28 16:35:48 -04:00
if self.cursor_y < self.rows.len() {
self.cursor_x = self.rows[self.cursor_y].chars.len();
}
}
_ => (),
};
2019-08-28 16:35:48 -04:00
return key;
}
// Continue the main input loop
Some(OtherKey('\0'))
}
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-28 16:35:48 -04:00
// Update the cursor position
match key {
PageUp => {
self.cursor_y = self.row_offset;
}
PageDown => {
self.cursor_y = self.row_offset + self.screen_rows - 1;
if self.cursor_y > self.rows.len() {
self.cursor_y = self.rows.len();
}
}
_ => (),
}
// Scroll the file up or down
while times > 1 {
times -= 1;
2019-08-28 16:35:48 -04:00
self.move_cursor(match key {
PageUp => &ArrowUp,
PageDown => &ArrowDown,
_ => &OtherKey('\0'),
})
}
2019-08-22 14:25:18 -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);
}
fn scroll(&mut self) {
2019-08-28 16:35:48 -04:00
self.render_x = 0;
if self.cursor_y < self.rows.len() {
self.render_x = self.row_cx_to_rx(self.cursor_y, self.cursor_x);
}
// Vertical scrolling
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;
}
2019-08-28 16:35:48 -04:00
// Horizontal scrolling
if self.render_x < self.col_offset {
self.col_offset = self.render_x;
}
if self.render_x >= self.col_offset + self.screen_cols {
self.col_offset = self.render_x - self.screen_cols + 1;
}
}
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 {
let file_row = y + self.row_offset;
if file_row >= 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;
}
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-28 16:35:48 -04:00
let mut len = self.rows[file_row].render.len() - self.col_offset;
if len > self.screen_cols {
len = self.screen_cols;
2019-08-27 12:22:19 -04:00
}
2019-08-28 16:35:48 -04:00
let output = self.rows[file_row].render.clone();
// let mut output = self.rows[file_row].render.clone();
// output.truncate(len);
self.append_out(&output[self.col_offset..len]);
2019-08-26 10:04:12 -04:00
}
self.append_out("\x1b[K");
2019-08-28 16:35:48 -04:00
self.append_out("\r\n");
2019-08-23 16:46:04 -04:00
}
}
2019-08-28 16:35:48 -04:00
fn draw_status_bar(&mut self) {
self.append_out("\x1b[7m");
let filename = if self.filename.is_empty() {
"[No Name]"
} else {
&self.filename
};
let mut left_message = format!("{:.20} - {} lines", filename, self.rows.len());
let mut right_message = format!("{}/{}", self.cursor_y + 1, self.rows.len());
let mut len = left_message.len();
let mut rlen = right_message.len();
2019-08-28 16:35:48 -04:00
if len > self.screen_cols {
2019-08-28 16:35:48 -04:00
len = self.screen_cols;
left_message.truncate(len);
2019-08-28 16:35:48 -04:00
}
self.append_out(&left_message);
2019-08-28 16:35:48 -04:00
for x in len..self.screen_cols {
if self.screen_cols - x == rlen {
self.append_out(&right_message);
break;
}
2019-08-28 16:35:48 -04:00
self.append_out(" ");
}
self.append_out("\x1b[m");
}
pub fn refresh_screen(&mut self) -> io::Result<()> {
self.scroll();
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
self.append_out("\x1b[?25l");
self.append_out("\x1b[H");
2019-08-23 16:46:04 -04:00
self.draw_rows();
2019-08-28 16:35:48 -04:00
self.draw_status_bar();
// Move cursor to state position
2019-08-28 16:35:48 -04:00
let y = (self.cursor_y - self.row_offset) + 1;
let x = (self.render_x - self.col_offset) + 1;
let cursor_code = format!("\x1b[{y};{x}H", y=y, x=x);
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();
handle.write_all(&self.output_buffer.as_bytes())
}
2019-08-27 08:30:51 -04:00
// ------------------------------------------------------------------------
// Row Operations
// ------------------------------------------------------------------------
2019-08-28 16:35:48 -04:00
fn row_cx_to_rx(&mut self, index: usize, cx: usize) -> usize {
let mut rx: usize = 0;
let row = &mut self.rows[index];
for char in row.chars.chars() {
if char == '\t' {
rx += (KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP);
}
rx += 1;
}
rx
}
fn update_row(&mut self, index: usize) {
let row = &mut self.rows[index];
let str = row.chars.clone();
// Cheat at rendering tabs as spaces
let str = str.replace('\t', " ");
row.render = str;
}
fn append_row(&mut self, row: &str) {
self.rows.push(EditorRow::new(row));
2019-08-28 16:35:48 -04:00
self.update_row(self.rows.len() - 1);
}
// ------------------------------------------------------------------------
// 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<()> {
2019-08-28 16:35:48 -04:00
self.filename = filename.to_owned();
let file = File::open(&self.filename)?;
2019-08-27 12:22:19 -04:00
let buf_reader = BufReader::new(file);
let lines = buf_reader.lines().map(|l| l.unwrap());
2019-08-27 12:22:19 -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
}