use crate::FileType; 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, file_type: FileType, } impl Document { pub fn open(filename: &str) -> Result { let contents = fs::read_to_string(filename)?; let file_type = FileType::from(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, file_type, }) } pub fn file_type(&self) -> String { self.file_type.name() } 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); } else 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); } self.unhighlight_rows(at.y); } #[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); } self.unhighlight_rows(at.y); } pub fn save(&mut self) -> Result<(), Error> { if let Some(file_name) = &self.file_name { let mut file = fs::File::create(file_name)?; self.file_type = FileType::from(file_name); for row in &mut self.rows { file.write_all(row.as_bytes())?; file.write_all(b"\n")?; } // File has been cleaned! (Saved) self.dirty = false; } Ok(()) } pub fn highlight(&mut self, word: &Option, until: Option) { let mut start_with_comment = false; let until = if let Some(until) = until { if until.saturating_add(1) < self.rows.len() { until.saturating_add(1) } else { self.rows.len() } } else { self.rows.len() }; #[allow(clippy::indexing_slicing)] for row in &mut self.rows[..until] { start_with_comment = row.highlight( &self.file_type.highlighting_options(), word, start_with_comment, ); } } 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 current_row = &mut self.rows[at.y]; let new_row = current_row.split(at.x); #[allow(clippy::integer_arithmetic)] self.rows.insert(at.y + 1, new_row); } fn unhighlight_rows(&mut self, start: usize) { let start = start.saturating_sub(1); for row in self.rows.iter_mut().skip(start) { row.is_highlighted = false; } } }