use crate::Position; use crate::Row; use crate::SearchDirection; use std::fs; use std::io::{Error, Write}; #[derive(Default)] pub struct Document { rows: Vec, pub file_name: Option, dirty: bool, } impl Document { pub fn open(filename: &str) -> Result { let contents = fs::read_to_string(filename)?; let mut rows = Vec::new(); for value in contents.lines() { rows.push(Row::from(value)); } Ok(Self { rows, file_name: Some(filename.to_string()), dirty: false, }) } pub fn row(&self, index: usize) -> Option<&Row> { self.rows.get(index) } pub fn is_empty(&self) -> bool { self.rows.is_empty() } pub fn len(&self) -> usize { self.rows.len() } pub fn insert(&mut self, at: &Position, c: char) { if at.y > self.rows.len() { return; } // File has been modified self.dirty = true; if c == '\n' { self.insert_newline(at); return; } if at.y == self.rows.len() { let mut row = Row::default(); row.insert(0, c); self.rows.push(row); } else { #[allow(clippy::indexing_slicing)] let row = &mut self.rows[at.y]; row.insert(at.x, c); } } #[allow(clippy::integer_arithmetic, clippy::indexing_slicing)] pub fn delete(&mut self, at: &Position) { let len = self.rows.len(); if at.y >= len { return; } // File has been modified self.dirty = true; if at.x == self.rows[at.y].len() && at.y + 1 < len { let next_row = self.rows.remove(at.y + 1); let row = &mut self.rows[at.y]; row.append(&next_row); } else { let row = &mut self.rows[at.y]; row.delete(at.x); } } pub fn save(&mut self) -> Result<(), Error> { if let Some(file_name) = &self.file_name { let mut file = fs::File::create(file_name)?; for row in &self.rows { file.write_all(row.as_bytes())?; file.write_all(b"\n")?; } // File has been cleaned! (Saved) self.dirty = false; } Ok(()) } pub fn is_dirty(&self) -> bool { self.dirty } #[allow(clippy::indexing_slicing)] pub fn find(&self, query: &str, at: &Position, direction: SearchDirection) -> Option { if at.y >= self.rows.len() { return None; } let mut position = Position { x: at.x, y: at.y }; let (start, end) = match direction { SearchDirection::Forward => (at.y, self.rows.len()), SearchDirection::Backward => (0, at.y.saturating_add(1)), }; for _ in start..end { if let Some(row) = self.rows.get(position.y) { if let Some(x) = row.find(&query, position.x, direction) { position.x = x; return Some(position); } if direction == SearchDirection::Forward { position.y = position.y.saturating_add(1); position.x = 0; } else { position.y = position.y.saturating_sub(1); position.x = self.rows[position.y].len(); } } else { return None; } } None } fn insert_newline(&mut self, at: &Position) { if at.y > self.rows.len() { return; } if at.y == self.rows.len() { self.rows.push(Row::default()); return; } #[allow(clippy::indexing_slicing)] let new_row = self.rows[at.y].split(at.x); #[allow(clippy::integer_arithmetic)] self.rows.insert(at.y + 1, new_row); } }