//! Editor functionality use crate::helpers::*; use std::cmp::PartialEq; use std::fs::File; use std::io; use std::io::prelude::*; use std::io::BufReader; use self::EditorKey::*; /// A representation of a line in the editor #[derive(Debug, Default)] pub struct EditorRow { chars: String, } impl EditorRow { pub fn new(str: &str) -> Self { EditorRow { chars: str.to_owned(), } } } /// Main structure for the editor /// `EditorConfig` struct in C version #[derive(Debug, Default)] pub struct Editor { cursor_x: usize, cursor_y: usize, rows: Vec, row_offset: usize, screen_cols: usize, screen_rows: usize, output_buffer: String, } /// Keycode mapping enum #[derive(Copy, Clone, Debug, PartialEq)] pub enum EditorKey { 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 { pub fn unwrap(self) -> char { match self { self::OtherKey(val) => val, _ => panic!("called `EditorKey::unwrap()` on a `None` value"), } } } impl Editor { // ------------------------------------------------------------------------ // Init // ------------------------------------------------------------------------ pub fn new() -> Self { let mut instance = Self::default(); let size = instance.get_window_size(); instance.rows = vec![]; instance.cursor_x = 0; instance.cursor_y = 0; instance.screen_cols = size.cols as usize; instance.screen_rows = size.rows as usize; instance } // ------------------------------------------------------------------------ // Terminal // ------------------------------------------------------------------------ fn read_key(&mut self) -> Option> { let stdin = io::stdin(); let stdin = stdin.lock(); let mut in_str = String::new(); let mut buffer = BufReader::with_capacity(4, stdin); buffer.read_to_string(&mut in_str).unwrap(); let mut input: Vec> = vec![]; for char in in_str.chars() { input.push(match char { '\x1b' => Escape, _ => OtherKey(char), }); } if input.is_empty() { return None; } 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, _ => input[2]// Escape, }; return Some(action); } }, 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 _ => input[2] //Escape, }; 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]), } } // 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 { match get_term_size() { Some(size) => size, None => unimplemented!("The easy way usually works"), } } // ------------------------------------------------------------------------ // Input // ------------------------------------------------------------------------ fn move_cursor(&mut self, key: &EditorKey) { 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.rows.len() { self.cursor_y += 1; } } _ => (), }; } /// Route user input to the appropriate handler method pub fn process_keypress(&mut self) -> Option> { 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; } } 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; } EndKey => { self.cursor_x = self.screen_cols - 1; }, _ => (), }; return key } // Continue the main input loop Some(OtherKey('\0')) } fn page_up_or_down(&mut self, key: EditorKey) { let mut times = self.screen_rows; while times > 1 { times -= 1; match key { PageUp => self.move_cursor(&ArrowUp), PageDown => self.move_cursor(&ArrowDown), _ => (), } } } // ------------------------------------------------------------------------ // Output // ------------------------------------------------------------------------ /// 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) { 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) { 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") ); 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 { self.append_out(" "); } self.append_out(&welcome); } else { self.append_out("~"); } } else { let mut output = self.rows[file_row].chars.clone(); if output.len() > self.screen_cols { output.truncate(self.screen_cols); } self.append_out(&output); } self.append_out("\x1b[K"); if y < (self.screen_rows - 1) { self.append_out("\r\n"); } } } pub fn refresh_screen(&mut self) -> io::Result<()> { self.scroll(); self.output_buffer.clear(); // Hide cursor, reposition cursor self.append_out("\x1b[?25l"); self.append_out("\x1b[H"); self.draw_rows(); // Move cursor to state position let cursor_code = format!("\x1b[{y};{x}H", y=self.cursor_y + 1, x=self.cursor_x + 1); self.append_out(&cursor_code); // Show cursor self.append_out("\x1b[?25h"); let stdout = io::stdout(); let mut handle = stdout.lock(); handle.write_all(&self.output_buffer.as_bytes()) } // ------------------------------------------------------------------------ // Row Operations // ------------------------------------------------------------------------ fn append_row(&mut self, row: &str) { self.rows.push(EditorRow::new(row)); } // ------------------------------------------------------------------------ // File I/O // ------------------------------------------------------------------------ /// Open a file for display pub fn open(&mut self, filename: &str) -> io::Result<()> { let file = File::open(filename)?; let buf_reader = BufReader::new(file); let lines = buf_reader.lines().map(|l| l.unwrap()); for line in lines { self.append_row(&line); } Ok(()) } }