2021-03-08 10:21:06 -05:00
|
|
|
use crate::Document;
|
2021-03-08 10:43:40 -05:00
|
|
|
use crate::Row;
|
2021-03-05 16:36:39 -05:00
|
|
|
use crate::Terminal;
|
2021-03-08 13:34:25 -05:00
|
|
|
use std::env;
|
2021-03-08 14:55:14 -05:00
|
|
|
use std::time::Duration;
|
|
|
|
use std::time::Instant;
|
2021-03-08 14:41:40 -05:00
|
|
|
use termion::color;
|
2021-01-28 16:47:40 -05:00
|
|
|
use termion::event::Key;
|
2021-03-05 16:36:39 -05:00
|
|
|
|
2021-03-08 14:41:40 -05:00
|
|
|
const STATUS_FG_COLOR: color::Rgb = color::Rgb(63, 63, 63);
|
|
|
|
const STATUS_BG_COLOR: color::Rgb = color::Rgb(239, 239, 239);
|
2021-03-05 16:36:39 -05:00
|
|
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
2021-01-28 16:47:40 -05:00
|
|
|
|
2021-03-08 10:21:06 -05:00
|
|
|
#[derive(Default)]
|
2021-03-08 09:50:15 -05:00
|
|
|
pub struct Position {
|
|
|
|
pub x: usize,
|
|
|
|
pub y: usize,
|
|
|
|
}
|
|
|
|
|
2021-03-08 14:55:14 -05:00
|
|
|
struct StatusMessage {
|
|
|
|
text: String,
|
|
|
|
time: Instant,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl StatusMessage {
|
|
|
|
fn from(message: String) -> Self {
|
|
|
|
Self {
|
|
|
|
time: Instant::now(),
|
|
|
|
text: message,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-28 16:47:40 -05:00
|
|
|
pub struct Editor {
|
|
|
|
should_quit: bool,
|
2021-03-05 16:36:39 -05:00
|
|
|
terminal: Terminal,
|
2021-03-08 09:50:15 -05:00
|
|
|
cursor_position: Position,
|
2021-03-08 14:21:24 -05:00
|
|
|
offset: Position,
|
2021-03-08 10:21:06 -05:00
|
|
|
document: Document,
|
2021-03-08 14:55:14 -05:00
|
|
|
status_message: StatusMessage,
|
2021-01-28 16:47:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Editor {
|
|
|
|
pub fn run(&mut self) {
|
|
|
|
loop {
|
2021-03-05 16:36:39 -05:00
|
|
|
if let Err(error) = self.refresh_screen() {
|
2021-01-28 16:47:40 -05:00
|
|
|
die(error);
|
|
|
|
}
|
|
|
|
if self.should_quit {
|
|
|
|
break;
|
|
|
|
}
|
2021-03-05 16:36:39 -05:00
|
|
|
if let Err(error) = self.process_keypress() {
|
|
|
|
die(error);
|
|
|
|
}
|
2021-01-28 16:47:40 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn default() -> Self {
|
2021-03-08 13:34:25 -05:00
|
|
|
let args: Vec<String> = env::args().collect();
|
2021-03-08 14:55:14 -05:00
|
|
|
let mut initial_status = String::from("HELP: Ctrl-Q = quit");
|
2021-03-08 13:34:25 -05:00
|
|
|
let document = if args.len() > 1 {
|
|
|
|
let file_name = &args[1];
|
2021-03-08 14:55:14 -05:00
|
|
|
let doc = Document::open(&file_name);
|
|
|
|
if doc.is_ok() {
|
|
|
|
doc.unwrap()
|
|
|
|
} else {
|
|
|
|
initial_status = format!("ERR: Could not open file: {}", file_name);
|
|
|
|
Document::default()
|
|
|
|
}
|
2021-03-08 13:34:25 -05:00
|
|
|
} else {
|
|
|
|
Document::default()
|
|
|
|
};
|
|
|
|
|
2021-03-05 16:36:39 -05:00
|
|
|
Self {
|
|
|
|
should_quit: false,
|
|
|
|
terminal: Terminal::default().expect("Failed to initialize terminal"),
|
2021-03-08 13:34:25 -05:00
|
|
|
document,
|
2021-03-08 10:21:06 -05:00
|
|
|
cursor_position: Position::default(),
|
2021-03-08 14:21:24 -05:00
|
|
|
offset: Position::default(),
|
2021-03-08 14:55:14 -05:00
|
|
|
status_message: StatusMessage::from(initial_status),
|
2021-03-05 16:36:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn refresh_screen(&self) -> Result<(), std::io::Error> {
|
|
|
|
Terminal::cursor_hide();
|
2021-03-08 10:21:06 -05:00
|
|
|
Terminal::cursor_position(&Position::default());
|
2021-03-05 16:36:39 -05:00
|
|
|
if self.should_quit {
|
|
|
|
Terminal::clear_screen();
|
|
|
|
println!("Goodbye.\r");
|
|
|
|
} else {
|
|
|
|
self.draw_rows();
|
2021-03-08 14:41:40 -05:00
|
|
|
self.draw_status_bar();
|
|
|
|
self.draw_message_bar();
|
2021-03-08 14:21:24 -05:00
|
|
|
Terminal::cursor_position(&Position {
|
|
|
|
x: self.cursor_position.x.saturating_sub(self.offset.x),
|
|
|
|
y: self.cursor_position.y.saturating_sub(self.offset.y),
|
|
|
|
});
|
2021-03-05 16:36:39 -05:00
|
|
|
}
|
|
|
|
Terminal::cursor_show();
|
|
|
|
Terminal::flush()
|
2021-01-28 16:47:40 -05:00
|
|
|
}
|
|
|
|
|
2021-03-08 14:41:40 -05:00
|
|
|
fn draw_status_bar(&self) {
|
|
|
|
let mut status;
|
|
|
|
let width = self.terminal.size().width as usize;
|
|
|
|
let mut file_name = "[No Name]".to_string();
|
|
|
|
|
|
|
|
if let Some(name) = &self.document.file_name {
|
|
|
|
file_name = name.clone();
|
|
|
|
file_name.truncate(20);
|
|
|
|
}
|
|
|
|
|
|
|
|
status = format!("{} - {} lines", file_name, self.document.len());
|
|
|
|
|
|
|
|
let line_indicator = format!(
|
|
|
|
"{}/{}",
|
|
|
|
self.cursor_position.y.saturating_add(1),
|
|
|
|
self.document.len()
|
|
|
|
);
|
|
|
|
let len = status.len() + line_indicator.len();
|
|
|
|
if width > len {
|
|
|
|
status.push_str(&" ".repeat(width - len));
|
|
|
|
}
|
|
|
|
status = format!("{}{}", status, line_indicator);
|
|
|
|
status.truncate(width);
|
|
|
|
|
|
|
|
Terminal::set_bg_color(STATUS_BG_COLOR);
|
|
|
|
Terminal::set_fg_color(STATUS_FG_COLOR);
|
|
|
|
println!("{}\r", status);
|
|
|
|
Terminal::reset_fg_color();
|
|
|
|
Terminal::reset_bg_color();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn draw_message_bar(&self) {
|
|
|
|
Terminal::clear_current_line();
|
2021-03-08 14:55:14 -05:00
|
|
|
let message = &self.status_message;
|
|
|
|
if Instant::now() - message.time < Duration::new(5, 0) {
|
|
|
|
let mut text = message.text.clone();
|
|
|
|
text.truncate(self.terminal.size().width as usize);
|
|
|
|
print!("{}", text);
|
|
|
|
}
|
2021-03-08 14:41:40 -05:00
|
|
|
}
|
|
|
|
|
2021-01-28 16:47:40 -05:00
|
|
|
fn process_keypress(&mut self) -> Result<(), std::io::Error> {
|
2021-03-05 16:36:39 -05:00
|
|
|
let pressed_key = Terminal::read_key()?;
|
2021-01-28 16:47:40 -05:00
|
|
|
match pressed_key {
|
|
|
|
Key::Ctrl('q') => self.should_quit = true,
|
2021-03-08 09:51:20 -05:00
|
|
|
Key::Up
|
|
|
|
| Key::Down
|
|
|
|
| Key::Left
|
|
|
|
| Key::Right
|
|
|
|
| Key::PageUp
|
|
|
|
| Key::PageDown
|
|
|
|
| Key::End
|
|
|
|
| Key::Home => self.move_cursor(pressed_key),
|
2021-01-28 16:47:40 -05:00
|
|
|
_ => (),
|
|
|
|
}
|
2021-03-08 14:21:24 -05:00
|
|
|
|
|
|
|
self.scroll();
|
|
|
|
|
2021-01-28 16:47:40 -05:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-03-08 14:21:24 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-08 09:50:15 -05:00
|
|
|
fn move_cursor(&mut self, key: Key) {
|
2021-03-08 14:21:24 -05:00
|
|
|
let terminal_height = self.terminal.size().height as usize;
|
2021-03-08 09:50:15 -05:00
|
|
|
let Position { mut y, mut x } = self.cursor_position;
|
2021-03-08 14:21:24 -05:00
|
|
|
let height = self.document.len();
|
|
|
|
let mut width = if let Some(row) = self.document.row(y) {
|
|
|
|
row.len()
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
2021-03-08 09:50:15 -05:00
|
|
|
|
|
|
|
match key {
|
|
|
|
Key::Up => y = y.saturating_sub(1),
|
|
|
|
Key::Down => {
|
|
|
|
if y < height {
|
|
|
|
y = y.saturating_add(1);
|
|
|
|
}
|
|
|
|
},
|
2021-03-08 14:21:24 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2021-03-08 09:50:15 -05:00
|
|
|
Key::Right => {
|
|
|
|
if x < width {
|
2021-03-08 14:21:24 -05:00
|
|
|
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
|
2021-03-08 09:50:15 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
Key::Home => x = 0,
|
|
|
|
Key::End => x = width,
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
|
2021-03-08 14:21:24 -05:00
|
|
|
width = if let Some(row) = self.document.row(y) {
|
|
|
|
row.len()
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
if x > width {
|
|
|
|
x = width;
|
|
|
|
}
|
|
|
|
|
2021-03-08 09:50:15 -05:00
|
|
|
self.cursor_position = Position { x, y }
|
|
|
|
}
|
|
|
|
|
|
|
|
fn draw_welcome_message(&self) {
|
|
|
|
let mut welcome_message = format!("Hecto editor -- version {}", VERSION);
|
|
|
|
let width = self.terminal.size().width as usize;
|
|
|
|
let len = welcome_message.len();
|
|
|
|
let padding = width.saturating_sub(len) / 2;
|
|
|
|
let spaces = " ".repeat(padding.saturating_sub(1));
|
|
|
|
welcome_message = format!("~{}{}", spaces, welcome_message);
|
|
|
|
welcome_message.truncate(width);
|
|
|
|
println!("{}\r", welcome_message);
|
|
|
|
}
|
|
|
|
|
2021-03-08 10:43:40 -05:00
|
|
|
pub fn draw_row(&self, row: &Row) {
|
2021-03-08 14:21:24 -05:00
|
|
|
let width = self.terminal.size().width as usize;
|
|
|
|
let start = self.offset.x;
|
|
|
|
let end = self.offset.x + width;
|
2021-03-08 10:43:40 -05:00
|
|
|
let row = row.render(start, end);
|
|
|
|
println!("{}\r", row);
|
|
|
|
}
|
|
|
|
|
2021-03-05 16:36:39 -05:00
|
|
|
fn draw_rows(&self) {
|
|
|
|
let height = self.terminal.size().height;
|
|
|
|
|
2021-03-08 14:21:24 -05:00
|
|
|
for terminal_row in 0..height {
|
2021-03-05 16:36:39 -05:00
|
|
|
Terminal::clear_current_line();
|
|
|
|
|
2021-03-08 14:21:24 -05:00
|
|
|
if let Some(row) = self.document.row(terminal_row as usize + self.offset.y) {
|
2021-03-08 10:43:40 -05:00
|
|
|
self.draw_row(row);
|
|
|
|
} else if self.document.is_empty() && terminal_row == height / 3 {
|
2021-03-08 09:50:15 -05:00
|
|
|
self.draw_welcome_message();
|
2021-03-05 16:36:39 -05:00
|
|
|
} else {
|
|
|
|
println!("~\r");
|
|
|
|
}
|
2021-01-28 16:47:40 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn die(e: std::io::Error) {
|
2021-03-05 16:36:39 -05:00
|
|
|
Terminal::clear_screen();
|
2021-01-28 16:47:40 -05:00
|
|
|
panic!(e);
|
|
|
|
}
|