From 7c68bc8a887c125eb2edd5b9017d87ac275b2f3e Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Fri, 12 Mar 2021 16:29:19 -0500 Subject: [PATCH] Finish chapter 6 (Search Functionality) --- src/document.rs | 40 +++++++++++++++++++++------- src/editor.rs | 69 ++++++++++++++++++++++++++++--------------------- src/main.rs | 7 ++--- src/row.rs | 31 +++++++++++++++++----- src/terminal.rs | 2 +- 5 files changed, 99 insertions(+), 50 deletions(-) diff --git a/src/document.rs b/src/document.rs index 08564f6..d27f3fb 100644 --- a/src/document.rs +++ b/src/document.rs @@ -1,5 +1,6 @@ use crate::Position; use crate::Row; +use crate::SearchDirection; use std::fs; use std::io::{Error, Write}; @@ -67,7 +68,7 @@ impl Document { pub fn delete(&mut self, at: &Position) { let len = self.rows.len(); if at.y >= len { - return + return; } // File has been modified @@ -103,15 +104,36 @@ impl Document { self.dirty } - pub fn find(&self, query: &str, after: &Position) -> Option { - let mut x = after.x; + #[allow(clippy::indexing_slicing)] + pub fn find(&self, query: &str, at: &Position, direction: SearchDirection) -> Option { + if at.y >= self.rows.len() { + return None; + } - for (y, row) in self.rows.iter().enumerate().skip(after.y) { - if let Some(x) = row.find(query, x) { - return Some(Position { x, y }) + 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; } - - x = 0; } None @@ -133,4 +155,4 @@ impl Document { #[allow(clippy::integer_arithmetic)] self.rows.insert(at.y + 1, new_row); } -} \ No newline at end of file +} diff --git a/src/editor.rs b/src/editor.rs index 04ab07b..a7ab8fd 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -12,6 +12,12 @@ const STATUS_BG_COLOR: color::Rgb = color::Rgb(239, 239, 239); const VERSION: &str = env!("CARGO_PKG_VERSION"); const QUIT_TIMES: u8 = 3; +#[derive(PartialEq, Copy, Clone)] +pub enum SearchDirection { + Forward, + Backward, +} + #[derive(Default, Clone)] pub struct Position { pub x: usize, @@ -63,7 +69,8 @@ impl Editor { pub fn default() -> Self { let args: Vec = env::args().collect(); - let mut initial_status = String::from("HELP: Ctrl-F = find | Ctrl-S = save | Ctrl-Q = quit"); + let mut initial_status = + String::from("HELP: Ctrl-F = find | Ctrl-S = save | Ctrl-Q = quit"); let document = if let Some(file_name) = args.get(1) { let doc = Document::open(&file_name); if let Ok(doc) = doc { @@ -161,7 +168,7 @@ impl Editor { fn save(&mut self) { if self.document.file_name.is_none() { - let new_name = self.prompt("Save as: ", |_,_,_| {}).unwrap_or(None); + let new_name = self.prompt("Save as: ", |_, _, _| {}).unwrap_or(None); if new_name.is_none() { self.status_message = StatusMessage::from("Save aborted."); return; @@ -180,7 +187,9 @@ impl Editor { fn search(&mut self) { let old_position = self.cursor_position.clone(); - if let Some(query) = self + let mut direction = SearchDirection::Forward; + + let query = self .prompt( "Search (ESC to cancel, arrows to navigate): ", |editor, key, query| { @@ -188,27 +197,29 @@ impl Editor { 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) { - editor.cursor_position = position; - editor.scroll(); - } else { - editor.move_cursor(Key::Left); - } - }) - .unwrap_or(None) - { - if let Some(position) = self.document.find(&query[..], &old_position) { - self.cursor_position = position; - } else { - self.status_message = StatusMessage::from(format!("Not found: {}", query)); - } - } else { + 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); + } + }, + ) + .unwrap_or(None); + + if query.is_none() { self.cursor_position = old_position; self.scroll(); } @@ -230,13 +241,13 @@ 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); - }, + } Key::Delete => self.document.delete(&self.cursor_position), Key::Backspace => { if self.cursor_position.x > 0 || self.cursor_position.y > 0 { @@ -266,9 +277,9 @@ impl Editor { Ok(()) } - fn prompt(&mut self, prompt: &str, callback: C) -> Result, std::io::Error> + fn prompt(&mut self, prompt: &str, mut callback: C) -> Result, std::io::Error> where - C: Fn(&mut Self, Key, &String), + C: FnMut(&mut Self, Key, &String), { let mut result = String::new(); @@ -337,7 +348,7 @@ impl Editor { if y < height { y = y.saturating_add(1); } - }, + } Key::Left => { if x > 0 { x -= 1; @@ -345,7 +356,7 @@ impl Editor { y -= 1; x = self.document.row(y).map_or(0, Row::len); } - }, + } Key::Right => { if x < width { x += 1; @@ -353,21 +364,21 @@ impl Editor { y += 1; x = 0; } - }, + } Key::PageUp => { - y = if y > terminal_height { + 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, _ => (), diff --git a/src/main.rs b/src/main.rs index 4580120..1c064a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,5 @@ #![warn(clippy::all, clippy::pedantic)] -#![allow( - clippy::missing_errors_doc, - clippy::must_use_candidate, - clippy::panic, -)] +#![allow(clippy::missing_errors_doc, clippy::must_use_candidate, clippy::panic)] mod document; mod editor; mod row; @@ -12,6 +8,7 @@ mod terminal; pub use document::Document; use editor::Editor; pub use editor::Position; +pub use editor::SearchDirection; pub use row::Row; pub use terminal::Terminal; diff --git a/src/row.rs b/src/row.rs index 9794252..9a4b70e 100644 --- a/src/row.rs +++ b/src/row.rs @@ -1,3 +1,4 @@ +use crate::SearchDirection; use std::cmp; use unicode_segmentation::UnicodeSegmentation; @@ -76,7 +77,7 @@ impl Row { #[allow(clippy::integer_arithmetic)] pub fn delete(&mut self, at: usize) { if at >= self.len() { - return + return; } let mut result = String::new(); @@ -127,20 +128,38 @@ impl Row { self.string.as_bytes() } - pub fn find(&self, query: &str, after: usize) -> Option { - let substring: String = self.string[..].graphemes(true).skip(after).collect(); - let matching_byte_index = substring.find(query); + pub fn find(&self, query: &str, at: usize, direction: SearchDirection) -> Option { + if at > self.len { + return None; + } + + let (start, end) = match direction { + SearchDirection::Forward => (at, self.len), + SearchDirection::Backward => (0, at), + }; + + let substring: String = self.string[..] + .graphemes(true) + .skip(start) + .take(end - start) + .collect(); + + let matching_byte_index = if direction == SearchDirection::Forward { + substring.find(query) + } else { + substring.rfind(query) + }; if let Some(matching_byte_index) = matching_byte_index { for (grapheme_index, (byte_index, _)) in substring[..].grapheme_indices(true).enumerate() { if matching_byte_index == byte_index { - return Some(after + grapheme_index); + return Some(start + grapheme_index); } } } None } -} \ No newline at end of file +} diff --git a/src/terminal.rs b/src/terminal.rs index a55a041..d7f5b96 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -37,7 +37,7 @@ impl Terminal { #[allow(clippy::cast_possible_truncation)] pub fn cursor_position(position: &Position) { - let Position{mut x, mut y} = position; + let Position { mut x, mut y } = position; x = x.saturating_add(1); y = y.saturating_add(1);