Added file path only search

This commit is contained in:
Joshua Barretto 2026-01-06 11:15:28 +00:00
parent b8de008862
commit 29ffab74ee
6 changed files with 101 additions and 59 deletions

View file

@ -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

View file

@ -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<usize>),
// 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<String>),
// Start a project-wide search with the given location and needle. `None` implies file path search.
OpenSearcher(PathBuf, Option<String>),
// 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<Action> {
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<Action> {
if matches!(
&self.0,

View file

@ -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);

View file

@ -25,6 +25,7 @@ use crate::{
pub enum CannotEnd {}
#[derive(Debug)]
pub struct Resp<End = CannotEnd> {
ended: Option<End>,
pub event: Option<Event>,

View file

@ -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)),
}

View file

@ -6,7 +6,7 @@ pub struct Searcher {
options: Options<SearchResult>,
path: PathBuf,
search_path: PathBuf,
needle: String,
needle: Option<String>,
// 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<String>) -> 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<SearchResult>,
) {
// 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<usize>,
}
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)
});