use std::{ ffi::OsStr, fmt::Write as FmtWrite, fs::File, io::{self, BufRead, BufReader, BufWriter, Write as IoWrite}, path::{Path, PathBuf}, }; use libterm::{Color, CursorStyle, Term}; use unicode_width::UnicodeWidthChar; use crate::{ config::Config, error::Error, line::{Line, TextLike}, }; #[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, dirty: bool, mode_dirty: bool, mode: Mode, view: View, name: Option, path: Option, 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; if line.display_width(config.tab_width) < self.width { 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>(path: P) -> io::Result> { let input = BufReader::new(File::open(path)?); let lines = input.lines().collect::, _>>()?; let lines = lines .into_iter() .map(|line| Line::from_str(line.trim_end_matches('\n'))) .collect(); Ok(lines) } pub fn open>(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![] }; Ok(Self { lines, name, path: Some(path.into()), mode: Mode::Normal, dirty: true, mode_dirty: true, view: View::default(), modified: false, }) } pub fn reopen>(&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>(&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() } pub fn is_empty(&self) -> bool { self.lines.is_empty() } 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); } 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); } 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, ) } pub fn width(&self) -> usize { self.view.width } pub fn height(&self) -> usize { self.view.height } pub fn is_modified(&self) -> bool { self.modified } pub fn is_dirty(&self) -> bool { self.dirty } fn display_line( &self, config: &Config, term: &mut Term, row: usize, line: &Line, ) -> Result<(), Error> { let mut pos = 0; term.set_cursor_position(row, self.view.offset_x)?; 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 { term.set_foreground(Color::Blue)?; term.write_char('>').map_err(Error::TerminalFmtError)?; term.set_foreground(Color::Default)?; } else { term.write_char(' ').map_err(Error::TerminalFmtError)?; } } } else { write!(term, "{}", ch).map_err(Error::TerminalFmtError)?; pos += ch.width().unwrap_or(1); } } if long_line { 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)?; } Ok(()) } pub fn display(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> { match self.mode { Mode::Normal => { term.set_cursor_style(CursorStyle::Default)?; } Mode::Insert => { term.set_cursor_style(CursorStyle::Line)?; } } for (row, line) in self .lines .iter() .skip(self.view.row_offset) .take(self.view.height) .enumerate() { 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)?, } } term.set_cursor_position(y, x + self.view.offset_x)?; self.mode_dirty = false; Ok(()) } pub fn newline_before(&mut self) { self.modified = true; 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; } self.modified = true; 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); self.modified = true; } 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; } 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; } pub fn number_width(&mut self) -> usize { if self.lines.is_empty() { 1 } else { self.lines.len().ilog10() as usize + 1 } } }