diff --git a/README.md b/README.md index f8426f0..50d5d41 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - [x] Search in buffer switcher - [x] File saving - [x] Syntax highlighting -- [x] Project search +- [x] Project search (including file path search) - [x] Auto-indent (and related features) - [x] Undo/redo - [x] Ability to resize panes diff --git a/src/action.rs b/src/action.rs index f6167e7..0bfa5ba 100644 --- a/src/action.rs +++ b/src/action.rs @@ -54,17 +54,17 @@ pub enum Action { // Switch the current pane to the given buffer SwitchBuffer(BufferId), // Open the file (on the given line) and switch the current pane to it - OpenFile(PathBuf, usize), + OpenFile(PathBuf, Option), // Create a new file and switch the current pane to it CreateFile(PathBuf), // Start a new command CommandStart(&'static str), // Go to the specified file line GotoLine(isize), - // Request to begin a search with the given needle - BeginSearch(String), - // Start a project-wide search with the given location and needle - OpenSearcher(PathBuf, String), + // Request to begin a search with the given needle. `None` implies file path search. + BeginSearch(Option), + // Start a project-wide search with the given location and needle. `None` implies file path search. + OpenSearcher(PathBuf, Option), // Fully select the token under the cursor SelectToken, // Fully select the entire input @@ -387,6 +387,22 @@ impl RawEvent { } } + pub fn to_path_search(&self) -> Option { + if matches!( + &self.0, + TerminalEvent::Key(KeyEvent { + code: KeyCode::Char('o'), + modifiers, + kind: KeyEventKind::Press, + .. + }) if *modifiers == KeyModifiers::CONTROL | KeyModifiers::SHIFT + ) { + Some(Action::BeginSearch(None)) + } else { + None + } + } + pub fn to_go(&self) -> Option { if matches!( &self.0, diff --git a/src/ui/doc.rs b/src/ui/doc.rs index b6e30e5..f06817c 100644 --- a/src/ui/doc.rs +++ b/src/ui/doc.rs @@ -83,6 +83,7 @@ impl Element for Doc { .or_else(|| e.to_open_finder(None)) .or_else(|| e.to_move()) .or_else(|| e.to_save()) + .or_else(|| e.to_path_search()) }) { action @ Some(Action::OpenSwitcher) | action @ Some(Action::OpenOpener(_)) => { Ok(Resp::handled(action.map(Into::into))) @@ -114,7 +115,9 @@ impl Element for Doc { Some(Action::OpenFile(path, line_idx)) => match state.open(path) { Ok(buffer_id) => { self.switch_buffer(state, buffer_id); - if let Some(buffer) = state.buffers.get_mut(self.buffer) { + if let Some(buffer) = state.buffers.get_mut(self.buffer) + && let Some(line_idx) = line_idx + { let cursor_id = self.cursors[&self.buffer]; buffer.goto_cursor(cursor_id, [0, line_idx as isize], true); self.input.refocus(buffer, cursor_id); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d55c8a7..1ec4937 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -25,6 +25,7 @@ use crate::{ pub enum CannotEnd {} +#[derive(Debug)] pub struct Resp { ended: Option, pub event: Option, diff --git a/src/ui/prompt.rs b/src/ui/prompt.rs index b87e69a..ebc21fd 100644 --- a/src/ui/prompt.rs +++ b/src/ui/prompt.rs @@ -56,7 +56,8 @@ impl Prompt { Ok(Action::GotoLine(line)) } Some(arg0 @ "search") => { - let needle = cmd.get(arg0.len()..).unwrap().trim().to_string(); + let needle = Some(cmd.get(arg0.len()..).unwrap().trim().to_string()) + .filter(|n| !n.is_empty()); Ok(Action::BeginSearch(needle)) } Some("reload") => Ok(Action::Reload), @@ -451,7 +452,7 @@ impl Element<()> for Opener { self.set_string(&format!("{}/", file.path.display())); Ok(Resp::handled(None)) }, - FileKind::File => Ok(Resp::end(Some(Action::OpenFile(file.path, 0).into()))), + FileKind::File => Ok(Resp::end(Some(Action::OpenFile(file.path, None).into()))), FileKind::New => Ok(Resp::end(Some(Action::CreateFile(file.path).into()))), FileKind::Unknown => Ok(Resp::handled(None)), } diff --git a/src/ui/search.rs b/src/ui/search.rs index b390097..918976e 100644 --- a/src/ui/search.rs +++ b/src/ui/search.rs @@ -6,7 +6,7 @@ pub struct Searcher { options: Options, path: PathBuf, search_path: PathBuf, - needle: String, + needle: Option, // Filter buffer: Buffer, cursor_id: CursorId, @@ -15,7 +15,7 @@ pub struct Searcher { } impl Searcher { - pub fn new(path: PathBuf, needle: String) -> Self { + pub fn new(path: PathBuf, needle: Option) -> Self { let mut search_path = path.clone(); let search_path = loop { if let Ok(mut entries) = fs::read_dir(&search_path) @@ -34,7 +34,7 @@ impl Searcher { fn search_in( search_path: &Path, path: &Path, - needle: &str, + needle: Option<&str>, results: &mut Vec, ) { // Cap reached! @@ -55,28 +55,42 @@ impl Searcher { && md.len() < 1 << 20 && let Ok(s) = fs::read_to_string(path) { - for (line_idx, line_text) in - s.lines().enumerate().filter(|(_, l)| l.contains(needle)) - { - let mut line_buffer = Buffer::new( - false, - line_text.trim().chars().collect(), - path.to_path_buf(), - ); + let rdir = format!( + "./{}", + path.parent() + .and_then(|p| p.strip_prefix(search_path).ok()?.to_str()) + .unwrap_or("unknown") + ); + if let Some(needle) = needle { + for (line_idx, line_text) in + s.lines().enumerate().filter(|(_, l)| l.contains(needle)) + { + let mut line_buffer = Buffer::new( + false, + line_text.trim().chars().collect(), + path.to_path_buf(), + ); + results.push(SearchResult { + loc: SearchLoc { + path: path.to_path_buf(), + line_idx: Some(line_idx), + }, + rdir: rdir.clone(), + line: Some(( + Input::search_result(line_idx), + line_buffer.start_session(), + line_buffer, + )), + }); + } + } else { results.push(SearchResult { loc: SearchLoc { path: path.to_path_buf(), - line_idx, + line_idx: None, }, - rdir: format!( - "./{}", - path.parent() - .and_then(|p| p.strip_prefix(search_path).ok()?.to_str()) - .unwrap_or("unknown") - ), - line_input: Input::search_result(line_idx), - line_cursor: line_buffer.start_session(), - line_buffer, + rdir, + line: None, }); } } else if let Ok(entries) = fs::read_dir(path) { @@ -98,7 +112,7 @@ impl Searcher { } let mut results = Vec::new(); - search_in(&search_path, &search_path, &needle, &mut results); + search_in(&search_path, &search_path, needle.as_deref(), &mut results); let mut buffer = Buffer::default(); let cursor_id = buffer.start_session(); @@ -210,15 +224,13 @@ impl Element<()> for Searcher { #[derive(Clone, PartialEq)] struct SearchLoc { path: PathBuf, - line_idx: usize, + line_idx: Option, } struct SearchResult { loc: SearchLoc, rdir: String, - line_input: Input, - line_cursor: CursorId, - line_buffer: Buffer, + line: Option<(Input, CursorId, Buffer)>, } impl Visual for SearchResult { @@ -240,14 +252,16 @@ impl Visual for SearchResult { .with_fg(state.theme.option_dir) .text([0, 0], &self.rdir); // Code snippet - self.line_input.render( - state, - None, - &self.line_buffer, - self.line_cursor, - None, - &mut frame.rect([col_a + col_b, 0], [!0, !0]), - ); + if let Some((input, cursor, buffer)) = &mut self.line { + input.render( + state, + None, + buffer, + *cursor, + None, + &mut frame.rect([col_a + col_b, 0], [!0, !0]), + ); + } } } @@ -270,8 +284,10 @@ impl Visual for Searcher { let mut buffer = Buffer::open(result.loc.path.clone()).ok()?; let cursor_id = buffer.start_session(); let mut input = Input::default(); - buffer.goto_cursor(cursor_id, [0, result.loc.line_idx as isize], true); - input.focus([0, result.loc.line_idx as isize - preview_sz as isize / 2]); + if let Some(line_idx) = result.loc.line_idx { + buffer.goto_cursor(cursor_id, [0, line_idx as isize], true); + input.focus([0, line_idx as isize - preview_sz as isize / 2]); + } Some((buffer, cursor_id, input, result.loc.clone())) }) }); @@ -291,20 +307,25 @@ impl Visual for Searcher { [frame.size()[0], path_input_sz], ) .with(|f| { - let title = format!( - "{} results for '{}' in {}/", - if self.options.ranking.is_empty() { - format!("No") - } else { - format!( - "{} of {}", - self.options.selected + 1, - self.options.ranking.len() - ) - }, - self.needle, - self.path.display() - ); + let num_results = if self.options.ranking.is_empty() { + format!("No") + } else { + format!( + "{} of {}", + self.options.selected + 1, + self.options.ranking.len() + ) + }; + let title = if let Some(needle) = &self.needle { + format!( + "{} results for '{}' in {}/", + num_results, + needle, + self.path.display() + ) + } else { + format!("{} results in {}/", num_results, self.path.display()) + }; self.input .render(state, Some(&title), &self.buffer, self.cursor_id, None, f) });