Scrolling

This commit is contained in:
Timothy Warren 2021-03-08 14:21:24 -05:00
parent 1c237c9cb9
commit a67f78680f
5 changed files with 125 additions and 18 deletions

View File

@ -8,3 +8,4 @@ edition = "2018"
[dependencies] [dependencies]
termion = "1" termion = "1"
unicode-segmentation = "1.7.1"

View File

@ -28,4 +28,8 @@ impl Document {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.rows.is_empty() self.rows.is_empty()
} }
pub fn len(&self) -> usize {
self.rows.len()
}
} }

View File

@ -16,6 +16,7 @@ pub struct Editor {
should_quit: bool, should_quit: bool,
terminal: Terminal, terminal: Terminal,
cursor_position: Position, cursor_position: Position,
offset: Position,
document: Document, document: Document,
} }
@ -48,6 +49,7 @@ impl Editor {
terminal: Terminal::default().expect("Failed to initialize terminal"), terminal: Terminal::default().expect("Failed to initialize terminal"),
document, document,
cursor_position: Position::default(), cursor_position: Position::default(),
offset: Position::default(),
} }
} }
@ -59,7 +61,10 @@ impl Editor {
println!("Goodbye.\r"); println!("Goodbye.\r");
} else { } else {
self.draw_rows(); 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::cursor_show();
Terminal::flush() Terminal::flush()
@ -79,14 +84,41 @@ impl Editor {
| Key::Home => self.move_cursor(pressed_key), | Key::Home => self.move_cursor(pressed_key),
_ => (), _ => (),
} }
self.scroll();
Ok(()) 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) { 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 Position { mut y, mut x } = self.cursor_position;
let size = self.terminal.size(); let height = self.document.len();
let height = size.height.saturating_sub(1) as usize; let mut width = if let Some(row) = self.document.row(y) {
let width = size.width.saturating_sub(1) as usize; row.len()
} else {
0
};
match key { match key {
Key::Up => y = y.saturating_sub(1), Key::Up => y = y.saturating_sub(1),
@ -95,19 +127,55 @@ impl Editor {
y = y.saturating_add(1); y = y.saturating_add(1);
} }
}, },
Key::Left => x = x.saturating_sub(1), Key::Left => {
Key::Right => { if x > 0 {
if x < width { x -= 1;
x = x.saturating_add(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::Home => x = 0,
Key::End => x = width, 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 } self.cursor_position = Position { x, y }
} }
@ -123,8 +191,9 @@ impl Editor {
} }
pub fn draw_row(&self, row: &Row) { pub fn draw_row(&self, row: &Row) {
let start = 0; let width = self.terminal.size().width as usize;
let end = self.terminal.size().width as usize; let start = self.offset.x;
let end = self.offset.x + width;
let row = row.render(start, end); let row = row.render(start, end);
println!("{}\r", row); println!("{}\r", row);
} }
@ -132,10 +201,10 @@ impl Editor {
fn draw_rows(&self) { fn draw_rows(&self) {
let height = self.terminal.size().height; let height = self.terminal.size().height;
for terminal_row in 0..height - 1 { for terminal_row in 0..height {
Terminal::clear_current_line(); 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); self.draw_row(row);
} else if self.document.is_empty() && terminal_row == height / 3 { } else if self.document.is_empty() && terminal_row == height / 3 {
self.draw_welcome_message(); self.draw_welcome_message();

View File

@ -1,14 +1,20 @@
use std::cmp; use std::cmp;
use unicode_segmentation::UnicodeSegmentation;
pub struct Row { pub struct Row {
string: String, string: String,
len: usize,
} }
impl From<&str> for Row { impl From<&str> for Row {
fn from(slice: &str) -> Self { fn from(slice: &str) -> Self {
Self { let mut row = Self {
string: String::from(slice), 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 { pub fn render(&self, start: usize, end: usize) -> String {
let end = cmp::min(end, self.string.len()); let end = cmp::min(end, self.string.len());
let start = cmp::min(start, end); 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();
} }
} }

View File

@ -20,7 +20,7 @@ impl Terminal {
Ok(Self { Ok(Self {
size: Size { size: Size {
width: size.0, width: size.0,
height: size.1, height: size.1.saturating_sub(2),
}, },
_stdout: stdout().into_raw_mode()?, _stdout: stdout().into_raw_mode()?,
}) })