Finish chapter 6 (Search Functionality)

This commit is contained in:
Timothy Warren 2021-03-12 16:29:19 -05:00
parent 5bd297ba27
commit 7c68bc8a88
5 changed files with 99 additions and 50 deletions

View File

@ -1,5 +1,6 @@
use crate::Position; use crate::Position;
use crate::Row; use crate::Row;
use crate::SearchDirection;
use std::fs; use std::fs;
use std::io::{Error, Write}; use std::io::{Error, Write};
@ -67,7 +68,7 @@ impl Document {
pub fn delete(&mut self, at: &Position) { pub fn delete(&mut self, at: &Position) {
let len = self.rows.len(); let len = self.rows.len();
if at.y >= len { if at.y >= len {
return return;
} }
// File has been modified // File has been modified
@ -103,15 +104,36 @@ impl Document {
self.dirty self.dirty
} }
pub fn find(&self, query: &str, after: &Position) -> Option<Position> { #[allow(clippy::indexing_slicing)]
let mut x = after.x; pub fn find(&self, query: &str, at: &Position, direction: SearchDirection) -> Option<Position> {
if at.y >= self.rows.len() {
for (y, row) in self.rows.iter().enumerate().skip(after.y) { return None;
if let Some(x) = row.find(query, x) {
return Some(Position { x, y })
} }
x = 0; 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 None

View File

@ -12,6 +12,12 @@ const STATUS_BG_COLOR: color::Rgb = color::Rgb(239, 239, 239);
const VERSION: &str = env!("CARGO_PKG_VERSION"); const VERSION: &str = env!("CARGO_PKG_VERSION");
const QUIT_TIMES: u8 = 3; const QUIT_TIMES: u8 = 3;
#[derive(PartialEq, Copy, Clone)]
pub enum SearchDirection {
Forward,
Backward,
}
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct Position { pub struct Position {
pub x: usize, pub x: usize,
@ -63,7 +69,8 @@ impl Editor {
pub fn default() -> Self { pub fn default() -> Self {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = 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 document = if let Some(file_name) = args.get(1) {
let doc = Document::open(&file_name); let doc = Document::open(&file_name);
if let Ok(doc) = doc { if let Ok(doc) = doc {
@ -180,7 +187,9 @@ impl Editor {
fn search(&mut self) { fn search(&mut self) {
let old_position = self.cursor_position.clone(); let old_position = self.cursor_position.clone();
if let Some(query) = self let mut direction = SearchDirection::Forward;
let query = self
.prompt( .prompt(
"Search (ESC to cancel, arrows to navigate): ", "Search (ESC to cancel, arrows to navigate): ",
|editor, key, query| { |editor, key, query| {
@ -188,27 +197,29 @@ impl Editor {
match key { match key {
Key::Right | Key::Down => { Key::Right | Key::Down => {
direction = SearchDirection::Forward;
editor.move_cursor(Key::Right); editor.move_cursor(Key::Right);
moved = true; moved = true;
} }
_ => (), Key::Left | Key::Up => direction = SearchDirection::Backward,
_ => direction = SearchDirection::Forward,
} }
if let Some(position) = editor.document.find(&query, &editor.cursor_position) { if let Some(position) =
editor
.document
.find(&query, &editor.cursor_position, direction)
{
editor.cursor_position = position; editor.cursor_position = position;
editor.scroll(); editor.scroll();
} else { } else if moved {
editor.move_cursor(Key::Left); editor.move_cursor(Key::Left);
} }
}) },
.unwrap_or(None) )
{ .unwrap_or(None);
if let Some(position) = self.document.find(&query[..], &old_position) {
self.cursor_position = position; if query.is_none() {
} else {
self.status_message = StatusMessage::from(format!("Not found: {}", query));
}
} else {
self.cursor_position = old_position; self.cursor_position = old_position;
self.scroll(); self.scroll();
} }
@ -230,13 +241,13 @@ impl Editor {
} }
self.should_quit = true self.should_quit = true
}, }
Key::Ctrl('s') => self.save(), Key::Ctrl('s') => self.save(),
Key::Ctrl('f') => self.search(), Key::Ctrl('f') => self.search(),
Key::Char(c) => { Key::Char(c) => {
self.document.insert(&self.cursor_position, c); self.document.insert(&self.cursor_position, c);
self.move_cursor(Key::Right); self.move_cursor(Key::Right);
}, }
Key::Delete => self.document.delete(&self.cursor_position), Key::Delete => self.document.delete(&self.cursor_position),
Key::Backspace => { Key::Backspace => {
if self.cursor_position.x > 0 || self.cursor_position.y > 0 { if self.cursor_position.x > 0 || self.cursor_position.y > 0 {
@ -266,9 +277,9 @@ impl Editor {
Ok(()) Ok(())
} }
fn prompt<C>(&mut self, prompt: &str, callback: C) -> Result<Option<String>, std::io::Error> fn prompt<C>(&mut self, prompt: &str, mut callback: C) -> Result<Option<String>, std::io::Error>
where where
C: Fn(&mut Self, Key, &String), C: FnMut(&mut Self, Key, &String),
{ {
let mut result = String::new(); let mut result = String::new();
@ -337,7 +348,7 @@ impl Editor {
if y < height { if y < height {
y = y.saturating_add(1); y = y.saturating_add(1);
} }
}, }
Key::Left => { Key::Left => {
if x > 0 { if x > 0 {
x -= 1; x -= 1;
@ -345,7 +356,7 @@ impl Editor {
y -= 1; y -= 1;
x = self.document.row(y).map_or(0, Row::len); x = self.document.row(y).map_or(0, Row::len);
} }
}, }
Key::Right => { Key::Right => {
if x < width { if x < width {
x += 1; x += 1;
@ -353,21 +364,21 @@ impl Editor {
y += 1; y += 1;
x = 0; x = 0;
} }
}, }
Key::PageUp => { Key::PageUp => {
y = if y > terminal_height { y = if y > terminal_height {
y.saturating_sub(terminal_height) y.saturating_sub(terminal_height)
} else { } else {
0 0
} }
}, }
Key::PageDown => { Key::PageDown => {
y = if y.saturating_add(terminal_height) < height { y = if y.saturating_add(terminal_height) < height {
y.saturating_add(terminal_height) y.saturating_add(terminal_height)
} else { } else {
height height
} }
}, }
Key::Home => x = 0, Key::Home => x = 0,
Key::End => x = width, Key::End => x = width,
_ => (), _ => (),

View File

@ -1,9 +1,5 @@
#![warn(clippy::all, clippy::pedantic)] #![warn(clippy::all, clippy::pedantic)]
#![allow( #![allow(clippy::missing_errors_doc, clippy::must_use_candidate, clippy::panic)]
clippy::missing_errors_doc,
clippy::must_use_candidate,
clippy::panic,
)]
mod document; mod document;
mod editor; mod editor;
mod row; mod row;
@ -12,6 +8,7 @@ mod terminal;
pub use document::Document; pub use document::Document;
use editor::Editor; use editor::Editor;
pub use editor::Position; pub use editor::Position;
pub use editor::SearchDirection;
pub use row::Row; pub use row::Row;
pub use terminal::Terminal; pub use terminal::Terminal;

View File

@ -1,3 +1,4 @@
use crate::SearchDirection;
use std::cmp; use std::cmp;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@ -76,7 +77,7 @@ impl Row {
#[allow(clippy::integer_arithmetic)] #[allow(clippy::integer_arithmetic)]
pub fn delete(&mut self, at: usize) { pub fn delete(&mut self, at: usize) {
if at >= self.len() { if at >= self.len() {
return return;
} }
let mut result = String::new(); let mut result = String::new();
@ -127,16 +128,34 @@ impl Row {
self.string.as_bytes() self.string.as_bytes()
} }
pub fn find(&self, query: &str, after: usize) -> Option<usize> { pub fn find(&self, query: &str, at: usize, direction: SearchDirection) -> Option<usize> {
let substring: String = self.string[..].graphemes(true).skip(after).collect(); if at > self.len {
let matching_byte_index = substring.find(query); 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 { if let Some(matching_byte_index) = matching_byte_index {
for (grapheme_index, (byte_index, _)) in for (grapheme_index, (byte_index, _)) in
substring[..].grapheme_indices(true).enumerate() substring[..].grapheme_indices(true).enumerate()
{ {
if matching_byte_index == byte_index { if matching_byte_index == byte_index {
return Some(after + grapheme_index); return Some(start + grapheme_index);
} }
} }
} }