diff --git a/src/document.rs b/src/document.rs index c10ea46..d68cd5a 100644 --- a/src/document.rs +++ b/src/document.rs @@ -10,7 +10,7 @@ pub struct Document { rows: Vec, pub file_name: Option, dirty: bool, - file_type: FileType + file_type: FileType, } impl Document { @@ -25,14 +25,18 @@ impl Document { rows, file_name: Some(filename.to_string()), dirty: false, - file_type + 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() } @@ -41,6 +45,21 @@ impl Document { self.rows.len() } + 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); + } + pub fn insert(&mut self, at: &Position, c: char) { if at.y > self.rows.len() { return; @@ -59,22 +78,15 @@ impl Document { } self.unhighlight_rows(at.y); } - - 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; + + 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; } - #[allow(clippy::indexing_slicing)] - let current_row = &mut self.rows[at.y]; - let new_row = current_row.split(at.x); - self.rows.insert(at.y + 1, new_row); } - - #[allow(clippy::indexing_slicing)] + + #[allow(clippy::integer_arithmetic, clippy::indexing_slicing)] pub fn delete(&mut self, at: &Position) { let len = self.rows.len(); if at.y >= len { @@ -89,9 +101,9 @@ impl Document { let row = &mut self.rows[at.y]; row.delete(at.x); } - self.unhighlight_rows(at.y) + 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)?; @@ -104,17 +116,18 @@ impl Document { } 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 = if direction == SearchDirection::Forward { at.y } else { @@ -144,7 +157,7 @@ impl Document { } None } - + pub fn highlight(&mut self, word: &Option, until: Option) { let mut start_with_comment = false; let until = if let Some(until) = until { @@ -158,18 +171,11 @@ impl Document { }; #[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); - } - } - - 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; + start_with_comment = row.highlight( + &self.file_type.highlighting_options(), + word, + start_with_comment, + ); } } - - pub fn file_type(&self) -> String { - self.file_type.name() - } } diff --git a/src/editor.rs b/src/editor.rs index 31ec585..bb532e2 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -2,12 +2,10 @@ use crate::Document; use crate::Row; use crate::Terminal; use std::env; -use std::u8; -use std::usize; -use termion::event::Key; -use termion::color; use std::time::Duration; use std::time::Instant; +use termion::color; +use termion::event::Key; const STATUS_FG_COLOR: color::Rgb = color::Rgb(63, 63, 63); const STATUS_BG_COLOR: color::Rgb = color::Rgb(239, 239, 239); @@ -17,38 +15,38 @@ const QUIT_TIMES: u8 = 3; #[derive(PartialEq, Copy, Clone)] pub enum SearchDirection { Forward, - Backward + Backward, } #[derive(Default, Clone)] pub struct Position { pub x: usize, - pub y: usize + pub y: usize, } struct StatusMessage { text: String, - time: Instant -} - -impl StatusMessage { - fn from(message: String) -> Self { - Self { - time: Instant::now(), - text: message - } - } + time: Instant, } pub struct Editor { should_quit: bool, terminal: Terminal, cursor_position: Position, - document: Document, offset: Position, + document: Document, status_message: StatusMessage, quit_times: u8, - highlighted_word: Option + highlighted_word: Option, +} + +impl StatusMessage { + fn from(message: String) -> Self { + Self { + time: Instant::now(), + text: message, + } + } } impl Editor { @@ -57,7 +55,6 @@ impl Editor { if let Err(error) = self.refresh_screen() { die(error); } - if self.should_quit { break; } @@ -81,7 +78,6 @@ impl Editor { } else { Document::default() }; - Self { should_quit: false, terminal: Terminal::default().expect("Failed to initialize terminal"), @@ -107,22 +103,74 @@ impl Editor { self.draw_message_bar(); Terminal::cursor_position(&Position { x: self.cursor_position.x.saturating_sub(self.offset.x), - y: self.cursor_position.y.saturating_sub(self.offset.y) + y: self.cursor_position.y.saturating_sub(self.offset.y), }); } Terminal::cursor_show(); Terminal::flush() } + fn save(&mut self) { + if self.document.file_name.is_none() { + let new_name = self.prompt("Save as: ", |_, _, _| {}).unwrap_or(None); + if new_name.is_none() { + self.status_message = StatusMessage::from("Save aborted.".to_string()); + return; + } + self.document.file_name = new_name; + } + if self.document.save().is_ok() { + self.status_message = StatusMessage::from("File saved successfully.".to_string()); + } else { + self.status_message = StatusMessage::from("Error writing file!".to_string()); + } + } + + fn search(&mut self) { + let old_position = self.cursor_position.clone(); + let mut direction = SearchDirection::Forward; + let query = self + .prompt( + "Search (ESC to cancel, Arrows to navigate): ", + |editor, key, query| { + let mut moved = false; + match key { + Key::Right | Key::Down => { + direction = SearchDirection::Forward; + editor.move_cursor(Key::Right); + moved = true; + } + Key::Left | Key::Up => direction = SearchDirection::Backward, + _ => direction = SearchDirection::Forward, + } + if let Some(position) = + editor + .document + .find(&query, &editor.cursor_position, direction) + { + editor.cursor_position = position; + editor.scroll(); + } else if moved { + editor.move_cursor(Key::Left); + } + editor.highlighted_word = Some(query.to_string()); + }, + ) + .unwrap_or(None); + if query.is_none() { + self.cursor_position = old_position; + self.scroll(); + } + self.highlighted_word = None; + } + fn process_keypress(&mut self) -> Result<(), std::io::Error> { let pressed_key = Terminal::read_key()?; match pressed_key { - Key::Ctrl('s') => self.save(), - Key::Ctrl('f') => self.search(), Key::Ctrl('q') => { if self.quit_times > 0 && self.document.is_dirty() { self.status_message = StatusMessage::from(format!( - "Warning! File has unsaved changes. Press Ctrl-Q {} more times to quit", + "WARNING! File has unsaved changes. Press Ctrl-Q {} more times to quit.", self.quit_times )); self.quit_times -= 1; @@ -130,6 +178,8 @@ impl Editor { } self.should_quit = true } + Key::Ctrl('s') => self.save(), + Key::Ctrl('f') => self.search(), Key::Char(c) => { self.document.insert(&self.cursor_position, c); self.move_cursor(Key::Right); @@ -147,9 +197,9 @@ impl Editor { | Key::Right | Key::PageUp | Key::PageDown - | Key::Home - | Key::End => self.move_cursor(pressed_key), - _ => () + | Key::End + | Key::Home => self.move_cursor(pressed_key), + _ => (), } self.scroll(); if self.quit_times < QUIT_TIMES { @@ -159,54 +209,32 @@ impl Editor { Ok(()) } - pub fn draw_row(&self, row: &Row) { + fn scroll(&mut self) { + let Position { x, y } = self.cursor_position; let width = self.terminal.size().width as usize; - let start = self.offset.x; - let end = self.offset.x.saturating_add(width); - let row = row.render(start, end); - println!("{}\r", row) - } - - #[allow(clippy::integer_division)] - fn draw_rows(&self) { - let height = self.terminal.size().height; - for terminal_row in 0..height { - Terminal::clear_current_line(); - if let Some(row) = self - .document - .row(self.offset.y.saturating_add(terminal_row as usize)) - { - self.draw_row(row); - } else if self.document.is_empty() && terminal_row == height / 3 { - self.draw_welcome_message(); - } else { - println!("~\r"); - } + let height = self.terminal.size().height as usize; + let 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); } - } - - fn draw_welcome_message(&self) { - let mut welcome_message = format!("Artem's editor -- version {}\r", VERSION); - let width = self.terminal.size().width as usize; - let len = welcome_message.len(); - #[allow(clippy::integer_division)] - 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); } fn move_cursor(&mut self, key: Key) { let terminal_height = self.terminal.size().height as usize; - let Position{ mut x, mut y } = self.cursor_position; + let Position { mut y, mut x } = self.cursor_position; let height = self.document.len(); let mut width = if let Some(row) = self.document.row(y) { row.len() } else { 0 }; - match key { Key::Up => y = y.saturating_sub(1), Key::Down => { @@ -225,7 +253,7 @@ impl Editor { x = 0; } } - }, + } Key::Right => { if x < width { x += 1; @@ -233,26 +261,25 @@ impl Editor { y += 1; x = 0; } - }, + } Key::PageUp => { y = if y > terminal_height { y.saturating_sub(terminal_height) } else { 0 } - }, + } Key::PageDown => { y = if y.saturating_add(terminal_height) < height { y.saturating_add(terminal_height) } else { height } - }, + } Key::Home => x = 0, Key::End => x = width, - _ => () + _ => (), } - width = if let Some(row) = self.document.row(y) { row.len() } else { @@ -261,26 +288,41 @@ impl Editor { if x > width { x = width; } - - self.cursor_position = Position{ x, y } + self.cursor_position = Position { x, y } } - fn scroll(&mut self) { - let Position {x, y} = self.cursor_position; + fn draw_welcome_message(&self) { + let mut welcome_message = format!("Hecto editor -- version {}", VERSION); let width = self.terminal.size().width as usize; - let height = self.terminal.size().height as usize; - let 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); + let len = welcome_message.len(); + #[allow(clippy::integer_arithmetic, clippy::integer_division)] + 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); + } + + pub fn draw_row(&self, row: &Row) { + let width = self.terminal.size().width as usize; + let start = self.offset.x; + let end = self.offset.x.saturating_add(width); + let row = row.render(start, end); + println!("{}\r", row) + } + + #[allow(clippy::integer_division, clippy::integer_arithmetic)] + fn draw_rows(&self) { + let height = self.terminal.size().height; + for terminal_row in 0..height { + Terminal::clear_current_line(); + if let Some(row) = self.document.row(self.offset.y.saturating_add(terminal_row as usize)) { + self.draw_row(row); + } else if self.document.is_empty() && terminal_row == height / 3 { + self.draw_welcome_message(); + } else { + println!("~\r"); + } } } @@ -292,28 +334,28 @@ impl Editor { } else { "" }; - 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, + "{} - {} lines{}", + file_name, self.document.len(), modified_indicator ); - status.truncate(width); let line_indicator = format!( "{} | {}/{}", self.document.file_type(), self.cursor_position.y.saturating_add(1), self.document.len() ); + #[allow(clippy::integer_arithmetic)] let len = status.len() + line_indicator.len(); status.push_str(&" ".repeat(width.saturating_sub(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); @@ -331,8 +373,8 @@ impl Editor { } } - fn prompt(&mut self, prompt: &str, mut callback: C) -> Result, std::io::Error> - where + fn prompt(&mut self, prompt: &str, mut callback: C) -> Result, std::io::Error> + where C: FnMut(&mut Self, Key, &String), { let mut result = String::new(); @@ -352,7 +394,7 @@ impl Editor { result.truncate(0); break; } - _ => () + _ => (), } callback(self, key, &result); } @@ -362,57 +404,6 @@ impl Editor { } Ok(Some(result)) } - - fn save(&mut self) { - if self.document.file_name.is_none() { - let new_name = self.prompt("Save as: ", |_, _, _| {}).unwrap_or(None); - if new_name.is_none() { - self.status_message = StatusMessage::from("Save aborted.".to_string()); - return; - } - self.document.file_name = new_name; - } - - if self.document.save().is_ok() { - self.status_message = StatusMessage::from("File saved successfully.".to_string()); - } else { - self.status_message = StatusMessage::from("Error writing file!".to_string()); - } - } - - fn search(&mut self) { - let old_position = self.cursor_position.clone(); - let mut direction = SearchDirection::Forward; - let query = self - .prompt( - "Search (ESC to cancel, Arrows to navigate): ", - |editor, key, query| { - let mut moved = false; - match key { - Key::Right | Key::Down => { - direction = SearchDirection::Forward; - editor.move_cursor(Key::Right); - moved = true; - } - Key::Left | Key::Up => direction = SearchDirection::Backward, - _ => direction = SearchDirection::Forward - } - if let Some(position) = editor.document.find(&query, &editor.cursor_position, direction) { - editor.cursor_position = position; - editor.scroll(); - } else if moved { - editor.move_cursor(Key::Left); - } - editor.highlighted_word = Some(query.to_string()); - } - ).unwrap_or(None); - - if query.is_none() { - self.cursor_position = old_position; - self.scroll(); - } - self.highlighted_word = None; - } } fn die(e: std::io::Error) { diff --git a/src/filetype.rs b/src/filetype.rs index fefa41c..43c40ad 100644 --- a/src/filetype.rs +++ b/src/filetype.rs @@ -1,6 +1,6 @@ pub struct FileType { name: String, - hl_opts: HighlightingOptions + hl_opts: HighlightingOptions, } #[derive(Default)] @@ -18,7 +18,7 @@ impl Default for FileType { fn default() -> Self { Self { name: String::from("No filetype"), - hl_opts: HighlightingOptions::default() + hl_opts: HighlightingOptions::default(), } } } @@ -36,7 +36,7 @@ impl FileType { if file_name.ends_with(".rs") { return Self { name: String::from("Rust"), - hl_opts: HighlightingOptions{ + hl_opts: HighlightingOptions { numbers: true, strings: true, characters: true, @@ -111,8 +111,8 @@ impl FileType { "f32".to_string(), "f64".to_string(), ], - } - } + }, + }; } Self::default() } diff --git a/src/highlighting.rs b/src/highlighting.rs index d11853b..3ee02cf 100644 --- a/src/highlighting.rs +++ b/src/highlighting.rs @@ -1,6 +1,6 @@ use termion::color; -#[derive(PartialEq, Clone, Copy)] +#[derive(PartialEq, Clone, Copy, Debug)] pub enum Type { None, Number, @@ -8,7 +8,7 @@ pub enum Type { String, Character, Comment, - MultulineComment, + MultilineComment, PrimaryKeywords, SecondaryKeywords, } @@ -18,9 +18,9 @@ impl Type { match self { Type::Number => color::Rgb(220, 163, 163), Type::Match => color::Rgb(38, 139, 210), - Type::String => color::Rgb(255, 255, 255), + Type::String => color::Rgb(211, 54, 130), Type::Character => color::Rgb(108, 113, 196), - Type::Comment | Type::MultulineComment => color::Rgb(133, 153, 9), + Type::Comment | Type::MultilineComment => color::Rgb(133, 153, 0), Type::PrimaryKeywords => color::Rgb(181, 137, 0), Type::SecondaryKeywords => color::Rgb(42, 161, 152), _ => color::Rgb(255, 255, 255), diff --git a/src/main.rs b/src/main.rs index a6cd4e8..2ed09b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,18 +11,18 @@ mod document; mod editor; mod filetype; +mod highlighting; mod row; mod terminal; -mod highlighting; use editor::Editor; pub use document::Document; -pub use row::Row; +pub use editor::Position; pub use editor::SearchDirection; pub use filetype::FileType; pub use filetype::HighlightingOptions; +pub use row::Row; pub use terminal::Terminal; -pub use editor::Position; fn main() { Editor::default().run(); diff --git a/src/row.rs b/src/row.rs index 85b6452..10a95a7 100644 --- a/src/row.rs +++ b/src/row.rs @@ -1,25 +1,25 @@ use crate::highlighting; use crate::HighlightingOptions; use crate::SearchDirection; -use std::{cmp, usize}; +use std::cmp; use termion::color; use unicode_segmentation::UnicodeSegmentation; #[derive(Default)] pub struct Row { string: String, - len: usize, highlighting: Vec, - pub is_highlighted: bool + pub is_highlighted: bool, + len: usize, } impl From<&str> for Row { fn from(slice: &str) -> Self { Self { string: String::from(slice), - len: slice.graphemes(true).count(), highlighting: Vec::new(), - is_highlighted: false + is_highlighted: false, + len: slice.graphemes(true).count(), } } } @@ -30,15 +30,14 @@ impl Row { let start = cmp::min(start, end); let mut result = String::new(); let mut current_highlighting = &highlighting::Type::None; - for (index, grapheme) in self.string[..].graphemes(true).enumerate().skip(start).take(end-start) { + #[allow(clippy::integer_arithmetic)] + for (index, grapheme) in self.string[..].graphemes(true).enumerate().skip(start).take(end - start) { if let Some(c) = grapheme.chars().next() { - let highlighting_type = self - .highlighting - .get(index) - .unwrap_or(&highlighting::Type::None); + let highlighting_type = self.highlighting.get(index).unwrap_or(&highlighting::Type::None); if highlighting_type != current_highlighting { current_highlighting = highlighting_type; - let start_highlight = format!("{}", termion::color::Fg(highlighting_type.to_color())); + let start_highlight = + format!("{}", termion::color::Fg(highlighting_type.to_color())); result.push_str(&start_highlight[..]); } if c == '\t' { @@ -116,14 +115,15 @@ impl Row { splitted_row.push_str(grapheme); } } + self.string = row; self.len = length; self.is_highlighted = false; Self { string: splitted_row, len: splitted_length, + is_highlighted: false, highlighting: Vec::new(), - is_highlighted: false } } @@ -145,6 +145,7 @@ impl Row { } else { at }; + #[allow(clippy::integer_arithmetic)] let substring: String = self.string[..] .graphemes(true) .skip(start) @@ -156,8 +157,11 @@ impl Row { substring.rfind(query) }; if let Some(matching_byte_index) = matching_byte_index { - for (grapheme_index, (byte_index, _)) in substring[..].grapheme_indices(true).enumerate() { + for (grapheme_index, (byte_index, _)) in + substring[..].grapheme_indices(true).enumerate() + { if matching_byte_index == byte_index { + #[allow(clippy::integer_arithmetic)] return Some(start + grapheme_index); } } @@ -175,7 +179,7 @@ impl Row { if let Some(next_index) = search_match.checked_add(word[..].graphemes(true).count()) { #[allow(clippy::indexing_slicing)] - for i in index.saturating_add(search_match)..next_index { + for i in search_match..next_index { self.highlighting[i] = highlighting::Type::Match; } index = next_index; @@ -205,10 +209,10 @@ impl Row { } true } - + fn highlight_keywords(&mut self, index: &mut usize, chars: &[char], keywords: &[String], hl_type: highlighting::Type) -> bool { if *index > 0 { - #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing, clippy::integer_arithmetic)] let prev_char = chars[*index - 1]; if !is_separator(prev_char) { return false; @@ -216,15 +220,14 @@ impl Row { } for word in keywords { if *index < chars.len().saturating_sub(word.len()) { - #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing, clippy::integer_arithmetic)] let next_char = chars[*index + word.len()]; if !is_separator(next_char) { continue; } } - if self.highlight_str(index, &word, chars, hl_type) { - return true + return true; } } false @@ -240,12 +243,7 @@ impl Row { } fn highlight_secondary_keywords(&mut self, index: &mut usize, opts: &HighlightingOptions, chars: &[char]) -> bool { - self.highlight_keywords( - index, - chars, - opts.secondary_keywords(), - highlighting::Type::SecondaryKeywords, - ) + self.highlight_keywords(index, chars, opts.secondary_keywords(), highlighting::Type::SecondaryKeywords) } fn highlight_char(&mut self, index: &mut usize, opts: &HighlightingOptions, c: char, chars: &[char]) -> bool { @@ -284,19 +282,20 @@ impl Row { } false } - + + #[allow(clippy::indexing_slicing, clippy::integer_arithmetic)] fn highlight_multiline_comment(&mut self, index: &mut usize, opts: &HighlightingOptions, c: char, chars: &[char]) -> bool { if opts.comments() && c == '/' && *index < chars.len() { if let Some(next_char) = chars.get(index.saturating_add(1)) { if *next_char == '*' { - let closing_index = if let Some(closing_index) = self.string[*index + 2..].find("*/") { - *index + closing_index + 4 - } else { - chars.len() - }; - + let closing_index = + if let Some(closing_index) = self.string[*index + 2..].find("*/") { + *index + closing_index + 4 + } else { + chars.len() + }; for _ in *index..closing_index { - self.highlighting.push(highlighting::Type::MultulineComment); + self.highlighting.push(highlighting::Type::MultilineComment); *index += 1; } return true; @@ -325,7 +324,7 @@ impl Row { } false } - + fn highlight_number(&mut self, index: &mut usize, opts: &HighlightingOptions, c: char, chars: &[char]) -> bool { if opts.numbers() && c.is_ascii_digit() { if *index > 0 { @@ -351,12 +350,12 @@ impl Row { false } - #[allow(clippy::indexing_slicing)] + #[allow(clippy::indexing_slicing, clippy::integer_arithmetic)] pub fn highlight(&mut self, opts: &HighlightingOptions, word: &Option, start_with_comment: bool) -> bool { let chars: Vec = self.string.chars().collect(); if self.is_highlighted && word.is_none() { if let Some(hl_type) = self.highlighting.last() { - if *hl_type == highlighting::Type::MultulineComment + if *hl_type == highlighting::Type::MultilineComment && self.string.len() > 1 && self.string[self.string.len() - 2..] == *"*/" { @@ -375,7 +374,7 @@ impl Row { chars.len() }; for _ in 0..closing_index { - self.highlighting.push(highlighting::Type::MultulineComment); + self.highlighting.push(highlighting::Type::MultilineComment); } index = closing_index; } @@ -391,9 +390,9 @@ impl Row { || self.highlight_secondary_keywords(&mut index, &opts, &chars) || self.highlight_string(&mut index, opts, *c, &chars) || self.highlight_number(&mut index, opts, *c, &chars) - { - continue; - } + { + continue; + } self.highlighting.push(highlighting::Type::None); index += 1; } @@ -406,7 +405,50 @@ impl Row { } } +fn is_separator(c: char) -> bool { + c.is_ascii_punctuation() || c.is_ascii_whitespace() +} + +#[cfg(test)] +mod test_super { + use super::*; -pub fn is_separator(c: char) -> bool { - c.is_ascii_punctuation() || c.is_ascii_whitespace() + #[test] + fn test_highlight_find() { + let mut row = Row::from("1testtest"); + row.highlighting = vec![ + highlighting::Type::Number, + highlighting::Type::None, + highlighting::Type::None, + highlighting::Type::None, + highlighting::Type::None, + highlighting::Type::None, + highlighting::Type::None, + highlighting::Type::None, + highlighting::Type::None, + ]; + row.highlight_match(&Some("t".to_string())); + assert_eq!( + vec![ + highlighting::Type::Number, + highlighting::Type::Match, + highlighting::Type::None, + highlighting::Type::None, + highlighting::Type::Match, + highlighting::Type::Match, + highlighting::Type::None, + highlighting::Type::None, + highlighting::Type::Match + ], + row.highlighting + ) + } + + #[test] + fn test_find() { + let row = Row::from("1testtest"); + assert_eq!(row.find("t", 0, SearchDirection::Forward), Some(1)); + assert_eq!(row.find("t", 2, SearchDirection::Forward), Some(4)); + assert_eq!(row.find("t", 5, SearchDirection::Forward), Some(5)); + } } diff --git a/src/terminal.rs b/src/terminal.rs index 71faaf8..cbae399 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,9 +1,9 @@ +use crate::Position; use std::io::{self, stdout, Write}; +use termion::color; use termion::event::Key; use termion::input::TermRead; use termion::raw::{IntoRawMode, RawTerminal}; -use crate::Position; -use termion::color; pub struct Size { pub width: u16, @@ -26,15 +26,15 @@ impl Terminal { _stdout: stdout().into_raw_mode()?, }) } - + pub fn size(&self) -> &Size { &self.size } - + pub fn clear_screen() { print!("{}", termion::clear::All); } - + #[allow(clippy::cast_possible_truncation)] pub fn cursor_position(position: &Position) { let Position { mut x, mut y } = position; @@ -56,31 +56,31 @@ impl Terminal { } } } - + pub fn cursor_hide() { print!("{}", termion::cursor::Hide); } - + pub fn cursor_show() { print!("{}", termion::cursor::Show); } - + pub fn clear_current_line() { print!("{}", termion::clear::CurrentLine); } - + pub fn set_bg_color(color: color::Rgb) { print!("{}", color::Bg(color)); } - + pub fn reset_bg_color() { print!("{}", color::Bg(color::Reset)); } - + pub fn set_fg_color(color: color::Rgb) { print!("{}", color::Fg(color)); } - + pub fn reset_fg_color() { print!("{}", color::Fg(color::Reset)); }