From a67f78680f184cde2cc2cc088ce07a4f0f4440ce Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Mon, 8 Mar 2021 14:21:24 -0500 Subject: [PATCH] Scrolling --- Cargo.toml | 1 + src/document.rs | 4 ++ src/editor.rs | 97 ++++++++++++++++++++++++++++++++++++++++++------- src/row.rs | 39 ++++++++++++++++++-- src/terminal.rs | 2 +- 5 files changed, 125 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 844a12f..7cd2d63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ edition = "2018" [dependencies] termion = "1" +unicode-segmentation = "1.7.1" diff --git a/src/document.rs b/src/document.rs index a51ceda..427e6d1 100644 --- a/src/document.rs +++ b/src/document.rs @@ -28,4 +28,8 @@ impl Document { pub fn is_empty(&self) -> bool { self.rows.is_empty() } + + pub fn len(&self) -> usize { + self.rows.len() + } } \ No newline at end of file diff --git a/src/editor.rs b/src/editor.rs index b1001ca..66c87d5 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -16,6 +16,7 @@ pub struct Editor { should_quit: bool, terminal: Terminal, cursor_position: Position, + offset: Position, document: Document, } @@ -48,6 +49,7 @@ impl Editor { terminal: Terminal::default().expect("Failed to initialize terminal"), document, cursor_position: Position::default(), + offset: Position::default(), } } @@ -59,7 +61,10 @@ impl Editor { println!("Goodbye.\r"); } else { self.draw_rows(); - Terminal::cursor_position(&self.cursor_position); + Terminal::cursor_position(&Position { + x: self.cursor_position.x.saturating_sub(self.offset.x), + y: self.cursor_position.y.saturating_sub(self.offset.y), + }); } Terminal::cursor_show(); Terminal::flush() @@ -79,14 +84,41 @@ impl Editor { | Key::Home => self.move_cursor(pressed_key), _ => (), } + + self.scroll(); + Ok(()) } + fn scroll(&mut self) { + let Position { x, y } = self.cursor_position; + let width = self.terminal.size().width as usize; + let height = self.terminal.size().height as usize; + + let mut offset = &mut self.offset; + + if y < offset.y { + offset.y = y; + } else if y >= offset.y.saturating_add(height) { + offset.y = y.saturating_sub(height).saturating_add(1); + } + + if x < offset.x { + offset.x = x; + } else if x >= offset.x.saturating_add(width) { + offset.x = x.saturating_sub(width).saturating_add(1); + } + } + fn move_cursor(&mut self, key: Key) { + let terminal_height = self.terminal.size().height as usize; let Position { mut y, mut x } = self.cursor_position; - let size = self.terminal.size(); - let height = size.height.saturating_sub(1) as usize; - let width = size.width.saturating_sub(1) as usize; + let height = self.document.len(); + let mut width = if let Some(row) = self.document.row(y) { + row.len() + } else { + 0 + }; match key { Key::Up => y = y.saturating_sub(1), @@ -95,19 +127,55 @@ impl Editor { y = y.saturating_add(1); } }, - Key::Left => x = x.saturating_sub(1), - Key::Right => { - if x < width { - x = x.saturating_add(1); + Key::Left => { + if x > 0 { + x -= 1; + } else if y > 0 { + y -= 1; + if let Some(row) = self.document.row(y) { + x = row.len() + } else { + x = 0 + } + } + }, + Key::Right => { + if x < width { + x += 1; + } else if y < height { + y += 1; + x = 0; + } + }, + Key::PageUp => { + y = if y > terminal_height { + y - terminal_height + } else { + 0 + } + }, + Key::PageDown => { + y = if y.saturating_add(terminal_height) < height { + y + terminal_height as usize + } else { + height } }, - Key::PageUp => y = 0, - Key::PageDown => y = height, Key::Home => x = 0, Key::End => x = width, _ => (), } + width = if let Some(row) = self.document.row(y) { + row.len() + } else { + 0 + }; + + if x > width { + x = width; + } + self.cursor_position = Position { x, y } } @@ -123,8 +191,9 @@ impl Editor { } pub fn draw_row(&self, row: &Row) { - let start = 0; - let end = self.terminal.size().width as usize; + let width = self.terminal.size().width as usize; + let start = self.offset.x; + let end = self.offset.x + width; let row = row.render(start, end); println!("{}\r", row); } @@ -132,10 +201,10 @@ impl Editor { fn draw_rows(&self) { let height = self.terminal.size().height; - for terminal_row in 0..height - 1 { + for terminal_row in 0..height { Terminal::clear_current_line(); - if let Some(row) = self.document.row(terminal_row as usize) { + if let Some(row) = self.document.row(terminal_row as usize + self.offset.y) { self.draw_row(row); } else if self.document.is_empty() && terminal_row == height / 3 { self.draw_welcome_message(); diff --git a/src/row.rs b/src/row.rs index 6d30a4d..ad0de0e 100644 --- a/src/row.rs +++ b/src/row.rs @@ -1,14 +1,20 @@ use std::cmp; +use unicode_segmentation::UnicodeSegmentation; pub struct Row { string: String, + len: usize, } impl From<&str> for Row { fn from(slice: &str) -> Self { - Self { + let mut row = Self { string: String::from(slice), - } + len: 0, + }; + row.update_len(); + row + } } @@ -16,6 +22,33 @@ impl Row { pub fn render(&self, start: usize, end: usize) -> String { let end = cmp::min(end, self.string.len()); let start = cmp::min(start, end); - self.string.get(start..end).unwrap_or_default().to_string() + + let mut result = String::new(); + + for grapheme in self.string[..] + .graphemes(true) + .skip(start) + .take(end - start) + { + if grapheme == "\t" { + result.push_str(" "); + } else { + result.push_str(grapheme); + } + } + + result + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + fn update_len(&mut self) { + self.len = self.string[..].graphemes(true).count(); } } \ No newline at end of file diff --git a/src/terminal.rs b/src/terminal.rs index 9f1b598..8472685 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -20,7 +20,7 @@ impl Terminal { Ok(Self { size: Size { width: size.0, - height: size.1, + height: size.1.saturating_sub(2), }, _stdout: stdout().into_raw_mode()?, })