Finish chapter 6 (Search Functionality)
This commit is contained in:
parent
5bd297ba27
commit
7c68bc8a88
@ -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() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
for (y, row) in self.rows.iter().enumerate().skip(after.y) {
|
let mut position = Position { x: at.x, y: at.y };
|
||||||
if let Some(x) = row.find(query, x) {
|
|
||||||
return Some(Position { x, 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
|
None
|
||||||
@ -133,4 +155,4 @@ impl Document {
|
|||||||
#[allow(clippy::integer_arithmetic)]
|
#[allow(clippy::integer_arithmetic)]
|
||||||
self.rows.insert(at.y + 1, new_row);
|
self.rows.insert(at.y + 1, new_row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
@ -161,7 +168,7 @@ impl Editor {
|
|||||||
|
|
||||||
fn save(&mut self) {
|
fn save(&mut self) {
|
||||||
if self.document.file_name.is_none() {
|
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() {
|
if new_name.is_none() {
|
||||||
self.status_message = StatusMessage::from("Save aborted.");
|
self.status_message = StatusMessage::from("Save aborted.");
|
||||||
return;
|
return;
|
||||||
@ -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.cursor_position = position;
|
editor
|
||||||
editor.scroll();
|
.document
|
||||||
} else {
|
.find(&query, &editor.cursor_position, direction)
|
||||||
editor.move_cursor(Key::Left);
|
{
|
||||||
}
|
editor.cursor_position = position;
|
||||||
})
|
editor.scroll();
|
||||||
.unwrap_or(None)
|
} else if moved {
|
||||||
{
|
editor.move_cursor(Key::Left);
|
||||||
if let Some(position) = self.document.find(&query[..], &old_position) {
|
}
|
||||||
self.cursor_position = position;
|
},
|
||||||
} else {
|
)
|
||||||
self.status_message = StatusMessage::from(format!("Not found: {}", query));
|
.unwrap_or(None);
|
||||||
}
|
|
||||||
} else {
|
if query.is_none() {
|
||||||
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,
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
31
src/row.rs
31
src/row.rs
@ -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,20 +128,38 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ impl Terminal {
|
|||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
pub fn cursor_position(position: &Position) {
|
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);
|
x = x.saturating_add(1);
|
||||||
y = y.saturating_add(1);
|
y = y.saturating_add(1);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user