534 lines
14 KiB
Rust
Raw Normal View History

2023-11-18 22:44:11 +02:00
use std::{
ffi::OsStr,
fs::File,
2023-11-20 11:21:25 +02:00
io::{self, BufRead, BufReader, BufWriter, Write as IoWrite},
fmt::Write as FmtWrite,
2023-11-18 22:44:11 +02:00
path::{Path, PathBuf},
};
use unicode_width::UnicodeWidthChar;
use crate::{
config::Config,
error::Error,
line::{Line, TextLike},
2023-11-20 11:21:25 +02:00
term::{Color, CursorStyle, Term},
2023-11-18 22:44:11 +02:00
};
#[derive(Default)]
pub struct View {
cursor_column: usize,
cursor_row: usize,
column_offset: usize,
row_offset: usize,
width: usize,
height: usize,
offset_x: usize,
}
#[derive(Clone, Copy, PartialEq)]
pub enum Mode {
Normal,
Insert,
}
pub enum SetMode {
Normal,
InsertBefore,
InsertAfter,
}
pub struct Buffer {
lines: Vec<Line>,
dirty: bool,
mode_dirty: bool,
mode: Mode,
view: View,
name: Option<String>,
path: Option<PathBuf>,
modified: bool,
}
impl Mode {
pub fn as_str(&self) -> &str {
match self {
Self::Normal => "NORMAL",
Self::Insert => "INSERT",
}
}
}
impl View {
pub fn set_row(&mut self, row: usize) {
self.cursor_row = row;
if self.cursor_row < self.row_offset {
self.row_offset = self.cursor_row;
} else if self.cursor_row >= self.row_offset + self.height {
self.row_offset = self.cursor_row - self.height + 1;
}
}
pub fn set_column(&mut self, config: &Config, col: usize, line: Option<&Line>) {
let Some(line) = line else {
self.column_offset = 0;
self.cursor_column = 0;
return;
};
self.cursor_column = col;
2023-11-20 13:03:02 +02:00
if line.display_width(config.tab_width) < self.width {
2023-11-18 22:44:11 +02:00
self.column_offset = 0;
return;
}
let width_to_cursor = line
.span(..self.cursor_column)
.display_width(config.tab_width);
if width_to_cursor < self.column_offset {
self.column_offset = width_to_cursor;
} else if width_to_cursor >= self.column_offset + self.width {
self.column_offset = width_to_cursor - self.width + 1;
}
}
pub fn reset(&mut self) {
self.column_offset = 0;
self.row_offset = 0;
self.cursor_row = 0;
self.cursor_column = 0;
}
// pub fn resize(&mut self, width: usize, height: usize) {
// self.width = width;
// self.height = height;
// self.column_offset = 0;
// self.row_offset = 0;
// }
}
impl Buffer {
pub fn empty() -> Self {
Self {
lines: vec![],
dirty: true,
mode_dirty: true,
view: View::default(),
mode: Mode::Normal,
name: None,
path: None,
modified: false,
}
}
fn read_lines<P: AsRef<Path>>(path: P) -> io::Result<Vec<Line>> {
let input = BufReader::new(File::open(path)?);
let lines = input.lines().collect::<Result<Vec<_>, _>>()?;
let lines = lines
.into_iter()
.map(|line| Line::from_str(line.trim_end_matches('\n')))
.collect();
Ok(lines)
}
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let path = path.as_ref();
let name = path.file_name().and_then(OsStr::to_str).map(String::from);
let lines = if path.exists() {
Self::read_lines(path)?
} else {
vec![]
};
Ok(Self {
lines,
name,
path: Some(path.into()),
mode: Mode::Normal,
dirty: true,
mode_dirty: true,
view: View::default(),
modified: false,
})
}
pub fn reopen<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
let path = path.as_ref();
let name = path.file_name().and_then(OsStr::to_str).map(String::from);
let lines = if path.exists() {
Self::read_lines(path)?
} else {
vec![]
};
self.lines = lines;
self.modified = false;
self.mode = Mode::Normal;
self.dirty = true;
self.mode_dirty = true;
self.view.reset();
self.path = Some(path.into());
self.name = name;
Ok(())
}
pub fn save(&mut self) -> Result<(), Error> {
let path = self.path.as_ref().ok_or(Error::NoPath)?;
let mut writer = BufWriter::new(File::create(path).map_err(Error::WriteError)?);
for line in self.lines.iter() {
writer
.write_all(line.to_string().as_ref())
.map_err(Error::WriteError)?;
writer.write_all(b"\n").map_err(Error::WriteError)?;
}
self.modified = false;
Ok(())
}
pub fn set_path<P: AsRef<Path>>(&mut self, path: P) {
let path = PathBuf::from(path.as_ref());
let name = path.file_name().and_then(OsStr::to_str).map(String::from);
self.path = Some(path);
self.name = name;
}
pub fn set_mode(&mut self, config: &Config, mode: SetMode) {
let dst_mode = match mode {
SetMode::Normal => Mode::Normal,
SetMode::InsertAfter | SetMode::InsertBefore => Mode::Insert,
};
if dst_mode == self.mode {
return;
}
self.mode = dst_mode;
self.mode_dirty = true;
match mode {
SetMode::Normal => self.move_cursor(config, -1, 0),
SetMode::InsertBefore => self.move_cursor(config, 0, 0),
SetMode::InsertAfter => self.move_cursor(config, 1, 0),
}
}
pub fn mode(&self) -> Mode {
self.mode
}
pub fn name(&self) -> Option<&String> {
self.name.as_ref()
}
pub fn path(&self) -> Option<&PathBuf> {
self.path.as_ref()
}
pub fn row_offset(&self) -> usize {
self.view.row_offset
}
pub fn len(&self) -> usize {
self.lines.len()
}
2023-11-20 13:03:02 +02:00
pub fn is_empty(&self) -> bool {
self.lines.is_empty()
}
2023-11-18 22:44:11 +02:00
pub fn cursor_row(&self) -> usize {
self.view.cursor_row
}
pub fn set_position(&mut self, config: &Config, px: usize, py: usize) {
self.dirty = true;
if self.lines.is_empty() {
self.view.reset();
return;
}
// Move to row
self.view.set_row(py.min(self.lines.len() - 1));
// Set mode- and line-len-adjusted column
if let Some(line) = self.lines.get(self.view.cursor_row) && !line.is_empty() {
match self.mode {
// Limited by line.len()
Mode::Normal => self.view.set_column(config, px.min(line.len() - 1), Some(line)),
Mode::Insert => self.view.set_column(config, px.min(line.len()), Some(line)),
}
} else {
self.view.set_column(config, 0, None);
}
}
pub fn to_line_end(&mut self, config: &Config) {
let len = self
.lines
.get(self.view.cursor_row)
.map(Line::len)
.unwrap_or(0);
self.set_position(config, len, self.view.cursor_row);
}
2023-11-20 13:03:02 +02:00
pub fn to_first_line(&mut self, config: &Config) {
let len = self
.lines
.get(self.view.cursor_row)
.map(Line::len)
.unwrap_or(0);
self.set_position(config, len, 0);
}
pub fn to_last_line(&mut self, config: &Config) {
if self.lines.is_empty() {
return;
}
let len = self
.lines
.get(self.view.cursor_row)
.map(Line::len)
.unwrap_or(0);
self.set_position(config, len, self.lines.len() - 1);
}
2023-11-18 22:44:11 +02:00
pub fn set_column(&mut self, config: &Config, x: usize) {
self.set_position(config, x, self.view.cursor_row);
}
pub fn move_cursor(&mut self, config: &Config, dx: isize, dy: isize) {
let px = (self.view.cursor_column as isize + dx).max(0) as usize;
let py = (self.view.cursor_row as isize + dy).max(0) as usize;
self.set_position(config, px, py);
}
pub fn resize(&mut self, config: &Config, offset_x: usize, width: usize, height: usize) {
self.dirty = true;
self.view.height = height;
self.view.width = width;
self.view.offset_x = offset_x;
self.view.set_row(self.view.cursor_row);
self.view.set_column(
config,
self.view.cursor_column,
self.lines.get(self.view.cursor_row),
);
}
pub fn display_cursor(&self, config: &Config) -> (usize, usize) {
if self.lines.is_empty() {
return (0, 0);
}
// assert!(self.view.column_offset <= self.view.cursor_column);
assert!(self.view.row_offset <= self.view.cursor_row);
let line = &self.lines[self.view.cursor_row];
assert!(self.view.cursor_column <= line.len());
let column = line
.span(..self.view.cursor_column)
.display_width(config.tab_width);
assert!(self.view.column_offset <= column);
(
column - self.view.column_offset,
self.view.cursor_row - self.view.row_offset,
)
}
2023-11-20 13:03:02 +02:00
pub fn width(&self) -> usize {
self.view.width
}
2023-11-18 22:44:11 +02:00
pub fn height(&self) -> usize {
self.view.height
}
pub fn is_modified(&self) -> bool {
self.modified
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
2023-11-20 11:21:25 +02:00
fn display_line(&self, config: &Config, term: &mut Term, row: usize, line: &Line) -> Result<(), Error> {
2023-11-18 22:44:11 +02:00
let mut pos = 0;
2023-11-20 11:21:25 +02:00
term.set_cursor_position(row, self.view.offset_x)?;
2023-11-18 22:44:11 +02:00
let span = line.skip_to_width(self.view.column_offset, config.tab_width);
let long_line = span.display_width(config.tab_width) > self.view.width;
for &ch in span.iter() {
if pos >= self.view.width {
break;
}
if ch == '\t' {
let old_pos = pos;
let new_pos = (pos + config.tab_width) & !(config.tab_width - 1);
pos = new_pos;
for i in old_pos..new_pos {
if i >= self.view.width {
break;
}
if i == old_pos {
2023-11-20 11:21:25 +02:00
term.set_foreground(Color::Blue)?;
term.write_char('>').map_err(Error::TerminalFmtError)?;
term.set_foreground(Color::Default)?;
2023-11-18 22:44:11 +02:00
} else {
2023-11-20 11:21:25 +02:00
term.write_char(' ').map_err(Error::TerminalFmtError)?;
2023-11-18 22:44:11 +02:00
}
}
} else {
2023-11-20 11:21:25 +02:00
write!(term, "{}", ch).map_err(Error::TerminalFmtError)?;
2023-11-18 22:44:11 +02:00
pos += ch.width().unwrap_or(1);
}
}
if long_line {
2023-11-20 11:21:25 +02:00
term.set_cursor_position(row, self.view.width + self.view.offset_x)?;
term.set_foreground(Color::Black)?;
term.set_background(Color::White)?;
term.write_char('>').map_err(Error::TerminalFmtError)?;
term.set_foreground(Color::Default)?;
term.set_background(Color::Default)?;
2023-11-18 22:44:11 +02:00
}
2023-11-20 11:21:25 +02:00
Ok(())
2023-11-18 22:44:11 +02:00
}
2023-11-20 11:21:25 +02:00
pub fn display(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> {
2023-11-18 22:44:11 +02:00
for (row, line) in self
.lines
.iter()
.skip(self.view.row_offset)
.take(self.view.height)
.enumerate()
{
2023-11-20 11:21:25 +02:00
self.display_line(config, term, row, line)?;
}
Ok(())
}
pub fn set_terminal_cursor(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> {
let (x, y) = self.display_cursor(config);
if self.mode_dirty {
match self.mode {
Mode::Normal => term.set_cursor_style(CursorStyle::Default)?,
Mode::Insert => term.set_cursor_style(CursorStyle::Line)?,
}
2023-11-18 22:44:11 +02:00
}
2023-11-20 11:21:25 +02:00
term.set_cursor_position(y, x + self.view.offset_x)?;
self.mode_dirty = false;
Ok(())
2023-11-18 22:44:11 +02:00
}
pub fn newline_before(&mut self) {
2023-11-20 11:21:25 +02:00
self.modified = true;
2023-11-18 22:44:11 +02:00
self.lines.insert(self.view.cursor_row, Line::new());
}
pub fn newline_after(&mut self, break_line: bool) {
if self.lines.is_empty() {
self.lines.push(Line::new());
return;
}
2023-11-20 11:21:25 +02:00
self.modified = true;
2023-11-18 22:44:11 +02:00
let newline = if break_line {
self.lines[self.view.cursor_row].split_off(self.view.cursor_column)
} else {
Line::new()
};
self.lines.insert(self.view.cursor_row + 1, newline);
}
pub fn insert(&mut self, config: &Config, ch: char) {
if self.lines.is_empty() {
assert_eq!(self.view.cursor_row, 0);
self.lines.push(Line::new());
}
let line = &mut self.lines[self.view.cursor_row];
line.insert(self.view.cursor_column, ch);
self.move_cursor(config, 1, 0);
self.modified = true;
}
pub fn erase_backward(&mut self, config: &Config) {
if self.lines.is_empty() {
return;
}
if self.view.cursor_column == 0 {
if self.view.cursor_row != 0 {
let line = self.lines.remove(self.view.cursor_row);
let prev_line = &mut self.lines[self.view.cursor_row - 1];
let len = prev_line.len();
prev_line.extend(line);
self.set_position(config, len, self.view.cursor_row - 1);
2023-11-20 11:21:25 +02:00
self.modified = true;
2023-11-18 22:44:11 +02:00
}
return;
}
let line = &mut self.lines[self.view.cursor_row];
line.remove(self.view.cursor_column - 1);
self.move_cursor(config, -1, 0);
self.modified = true;
}
pub fn erase_forward(&mut self) {
if self.lines.is_empty() {
return;
}
let line = &mut self.lines[self.view.cursor_row];
if self.view.cursor_column == line.len() {
return;
}
line.remove(self.view.cursor_column);
self.dirty = true;
self.modified = true;
}
2023-11-20 13:03:02 +02:00
pub fn kill_line(&mut self, config: &Config) {
if self.lines.is_empty() {
return;
}
self.lines.remove(self.view.cursor_row);
self.move_cursor(config, 0, 1);
self.modified = true;
}
2023-11-18 22:44:11 +02:00
pub fn number_width(&mut self) -> usize {
2023-11-20 13:03:02 +02:00
if self.lines.is_empty() {
2023-11-18 22:44:11 +02:00
1
} else {
self.lines.len().ilog10() as usize + 1
}
}
}