//! 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::*; #[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 /// /// impl blocks are split similarly to the original C implementation #[derive(Debug, Default)] pub struct Editor { cursor_x: usize, cursor_y: usize, row: EditorRow, num_rows: usize, screen_cols: usize, screen_rows: usize, output_buffer: String, } #[derive(Copy, Clone, Debug, PartialEq)] enum EditorKey { Escape, ArrowLeft, ArrowRight, ArrowUp, ArrowDown, DeleteKey, HomeKey, EndKey, PageUp, PageDown, OtherKey(T), } impl EditorKey { pub fn unwrap(self) -> char { match self { self::OtherKey(val) => val, _ => panic!("called `EditorKey::unwrap()` on a `None` value"), } } } // init impl Editor { pub fn new() -> Self { let mut instance = Self::default(); let size = instance.get_window_size(); instance.cursor_x = 0; instance.cursor_y = 0; instance.num_rows = 0; instance.screen_cols = size.cols as usize; instance.screen_rows = size.rows as usize; instance } } // Terminal impl Editor { fn read_key(&mut self) -> Option>> { let stdin = io::stdin(); let stdin = stdin.lock(); let mut in_str = String::new(); let mut input = BufReader::with_capacity(3, stdin); input.read_to_string(&mut in_str).unwrap(); let mut output: Vec> = vec![]; for char in in_str.chars() { output.push(match char { '\x1b' => Escape, _ => OtherKey(char), }); } if output.len() == 0 { 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); } fn get_window_size(&mut self) -> TermSize { match get_term_size() { Some(size) => size, None => unimplemented!("The easy way usually works"), } } } // Input impl Editor { 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.screen_rows - 1 { self.cursor_y += 1; } } _ => (), }; } /// Route user input to the appropriate handler method pub fn process_keypress(&mut self) -> Option<()> { let chars = self.read_key(); if chars.is_none() { Some(()) // Continue input loop on empty input } else { let chars = chars.unwrap(); // print!("{:?}\r\n", &chars); let first = &chars[0]; match first { 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), _ => (), }; // Continue the main input loop Some(()) } } 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 impl Editor { /// 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 draw_rows(&mut self) { for y in 0..self.screen_rows { if y >= self.num_rows { if y == (self.screen_rows / 3) { let mut welcome = format!("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; } while padding > 0 { self.append_out(" "); padding -= 1; } self.append_out(&welcome); } else { self.append_out("~"); } } else { let mut output = self.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.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[{};{}H", self.cursor_y + 1, 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()) } } // File I/O impl Editor { pub fn open(&mut self, filename: &str) -> io::Result<()> { let file = File::open(filename)?; let buf_reader = BufReader::new(file); let mut lines = buf_reader.lines().map(|l| l.unwrap()); let line = lines.next().unwrap(); self.row = EditorRow::new(&line); self.num_rows = 1; /*for line in lines { let }*/ // let line_res = buf_reader.read_line() // self.row = EditorRow::new("Hello, world!"); // self.num_rows += 1; Ok(()) } }