diff --git a/src/editor.rs b/src/editor.rs index f7f175b..b7c03f8 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -6,20 +6,25 @@ use std::fs::File; use std::io; use std::io::prelude::*; use std::io::BufReader; +use std::string::ToString; use self::EditorKey::*; +const KILO_TAB_STOP: usize = 4; + /// A representation of a line in the editor #[derive(Debug, Default)] pub struct EditorRow { chars: String, + render: String, } impl EditorRow { - pub fn new(str: &str) -> Self { - EditorRow { - chars: str.to_owned(), - } + pub fn new(chars: &str) -> Self { + let mut instance = EditorRow::default(); + instance.chars = chars.to_owned(); + + instance } } @@ -29,10 +34,13 @@ impl EditorRow { pub struct Editor { cursor_x: usize, cursor_y: usize, - rows: Vec, + render_x: usize, + col_offset: usize, row_offset: usize, screen_cols: usize, screen_rows: usize, + rows: Vec, + filename: String, output_buffer: String, } @@ -76,7 +84,7 @@ impl Editor { instance.cursor_x = 0; instance.cursor_y = 0; instance.screen_cols = size.cols as usize; - instance.screen_rows = size.rows as usize; + instance.screen_rows = (size.rows - 1) as usize; instance } @@ -135,12 +143,12 @@ impl Editor { '6' => PageDown, '7' => HomeKey, '8' => EndKey, - _ => input[2]// Escape, + _ => input[2], // Escape, }; return Some(action); } - }, + } 3 => { match input[1] { // Escape code of the form `^[[X` @@ -154,11 +162,11 @@ impl Editor { 'F' => EndKey, // Eh, just return escape otherwise - _ => input[2] //Escape, + _ => input[2], //Escape, }; return Some(action); - }, + } // Escape code of the form `^[OX` OtherKey('O') => { let action = match input[2].unwrap() { @@ -168,14 +176,14 @@ impl Editor { 'Q' => Function('2'), 'R' => Function('3'), 'S' => Function('4'), - _ => input[2] //Escape, + _ => input[2], //Escape, }; return Some(action); - }, - _ => return Some(input[1]) + } + _ => return Some(input[1]), } - }, + } _ => return Some(input[0]), } } @@ -197,15 +205,26 @@ impl Editor { // ------------------------------------------------------------------------ fn move_cursor(&mut self, key: &EditorKey) { + let row = self.rows.get(self.cursor_y); match key { ArrowLeft => { - if self.cursor_x > 0 { + if self.cursor_x != 0 { + // Move cursor left self.cursor_x -= 1; + } 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(); } } ArrowRight => { - if self.cursor_x < self.screen_cols - 1 { + if row.is_some() && self.cursor_x < row.unwrap().chars.len() { + // Move cursor right self.cursor_x += 1; + } 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; } } ArrowUp => { @@ -220,6 +239,18 @@ impl Editor { } _ => (), }; + + 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; + } } /// Route user input to the appropriate handler method @@ -248,12 +279,14 @@ impl Editor { self.cursor_x = 0; } EndKey => { - self.cursor_x = self.screen_cols - 1; - }, + if self.cursor_y < self.rows.len() { + self.cursor_x = self.rows[self.cursor_y].chars.len(); + } + } _ => (), }; - return key + return key; } // Continue the main input loop @@ -263,13 +296,28 @@ impl Editor { fn page_up_or_down(&mut self, key: EditorKey) { let mut times = self.screen_rows; + // 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; - match key { - PageUp => self.move_cursor(&ArrowUp), - PageDown => self.move_cursor(&ArrowDown), - _ => (), - } + self.move_cursor(match key { + PageUp => &ArrowUp, + PageDown => &ArrowDown, + _ => &OtherKey('\0'), + }) } } @@ -286,13 +334,26 @@ impl Editor { } fn scroll(&mut self) { + 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; } + + // 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; + } } fn draw_rows(&mut self) { @@ -323,20 +384,45 @@ impl Editor { self.append_out("~"); } } else { - let mut output = self.rows[file_row].chars.clone(); - if output.len() > self.screen_cols { - output.truncate(self.screen_cols); + let mut len = self.rows[file_row].render.len() - self.col_offset; + if len > self.screen_cols { + len = self.screen_cols; } - self.append_out(&output); + + 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]); } self.append_out("\x1b[K"); - if y < (self.screen_rows - 1) { - self.append_out("\r\n"); - } + self.append_out("\r\n"); } } + 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 message = format!("{:.20} - {} lines", filename, self.rows.len()); + let mut len = message.len(); + if len > self.screen_cols { + len = self.screen_cols; + message.truncate(len); + } + self.append_out(&message); + + for _ in len..self.screen_cols { + self.append_out(" "); + } + self.append_out("\x1b[m"); + } + pub fn refresh_screen(&mut self) -> io::Result<()> { self.scroll(); self.output_buffer.clear(); @@ -346,9 +432,12 @@ impl Editor { self.append_out("\x1b[H"); self.draw_rows(); + self.draw_status_bar(); // Move cursor to state position - let cursor_code = format!("\x1b[{y};{x}H", y=self.cursor_y + 1, x=self.cursor_x + 1); + 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 @@ -363,8 +452,32 @@ impl Editor { // Row Operations // ------------------------------------------------------------------------ + 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)); + self.update_row(self.rows.len() - 1); } // ------------------------------------------------------------------------ @@ -373,7 +486,8 @@ impl Editor { /// Open a file for display pub fn open(&mut self, filename: &str) -> io::Result<()> { - let file = File::open(filename)?; + self.filename = filename.to_owned(); + let file = File::open(&self.filename)?; let buf_reader = BufReader::new(file); let lines = buf_reader.lines().map(|l| l.unwrap());