feat: find

This commit is contained in:
sxyazi 2023-09-04 01:34:07 +08:00
parent 0acf345ee3
commit ac20e79a84
No known key found for this signature in database
14 changed files with 185 additions and 27 deletions

View file

@ -2,7 +2,7 @@ use core::{emit, files::FilesOp, input::InputMode, Event};
use std::ffi::OsString; use std::ffi::OsString;
use anyhow::{Ok, Result}; use anyhow::{Ok, Result};
use config::{keymap::{Control, Key, KeymapLayer}, BOOT}; use config::{keymap::{Exec, Key, KeymapLayer}, BOOT};
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use shared::{expand_url, Term}; use shared::{expand_url, Term};
use tokio::sync::oneshot; use tokio::sync::oneshot;
@ -34,7 +34,7 @@ impl App {
Event::Render(_) => app.dispatch_render(), Event::Render(_) => app.dispatch_render(),
Event::Resize(..) => app.dispatch_resize(), Event::Resize(..) => app.dispatch_resize(),
Event::Stop(state, tx) => app.dispatch_stop(state, tx), Event::Stop(state, tx) => app.dispatch_stop(state, tx),
Event::Ctrl(ctrl, layer) => app.dispatch_ctrl(ctrl, layer), Event::Call(exec, layer) => app.dispatch_call(exec, layer),
event => app.dispatch_module(event), event => app.dispatch_module(event),
} }
} }
@ -109,8 +109,8 @@ impl App {
} }
#[inline] #[inline]
fn dispatch_ctrl(&mut self, ctrl: Control, layer: KeymapLayer) { fn dispatch_call(&mut self, exec: Vec<Exec>, layer: KeymapLayer) {
if Executor::dispatch(&mut self.cx, &ctrl.exec, layer) { if Executor::dispatch(&mut self.cx, &exec, layer) {
emit!(Render); emit!(Render);
} }
} }

View file

@ -37,7 +37,7 @@ impl Executor {
} }
#[inline] #[inline]
pub(super) fn dispatch(cx: &mut Ctx, exec: &Vec<Exec>, layer: KeymapLayer) -> bool { pub(super) fn dispatch(cx: &mut Ctx, exec: &[Exec], layer: KeymapLayer) -> bool {
let mut render = false; let mut render = false;
for e in exec { for e in exec {
render |= match layer { render |= match layer {
@ -138,6 +138,14 @@ impl Executor {
_ => false, _ => false,
}, },
// Find
"find" => {
let query = exec.args.get(0).map(|s| s.as_str());
let prev = exec.named.contains_key("previous");
cx.manager.active_mut().find(query, prev)
}
"find_arrow" => cx.manager.active_mut().find_arrow(exec.named.contains_key("previous")),
// Sorting // Sorting
"sort" => { "sort" => {
let b = cx.manager.active_mut().set_sorter(FilesSorter { let b = cx.manager.active_mut().set_sorter(FilesSorter {

View file

@ -11,11 +11,12 @@ pub(super) struct Folder<'a> {
folder: &'a core::manager::Folder, folder: &'a core::manager::Folder,
is_preview: bool, is_preview: bool,
is_selection: bool, is_selection: bool,
is_find: bool,
} }
impl<'a> Folder<'a> { impl<'a> Folder<'a> {
pub(super) fn new(cx: &'a Ctx, folder: &'a core::manager::Folder) -> Self { pub(super) fn new(cx: &'a Ctx, folder: &'a core::manager::Folder) -> Self {
Self { cx, folder, is_preview: false, is_selection: false } Self { cx, folder, is_preview: false, is_selection: false, is_find: false }
} }
#[inline] #[inline]
@ -30,6 +31,24 @@ impl<'a> Folder<'a> {
self self
} }
#[inline]
pub(super) fn with_find(mut self, state: bool) -> Self {
self.is_find = state;
self
}
}
impl<'a> Folder<'a> {
#[inline]
fn icon(file: &File) -> &'static str {
THEME
.icons
.iter()
.find(|x| x.name.match_path(file.path(), Some(file.is_dir())))
.map(|x| x.display.as_ref())
.unwrap_or("")
}
#[inline] #[inline]
fn file_style(&self, file: &File) -> Style { fn file_style(&self, file: &File) -> Style {
let mimetype = &self.cx.manager.mimetype; let mimetype = &self.cx.manager.mimetype;
@ -57,14 +76,7 @@ impl<'a> Widget for Folder<'a> {
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, f)| { .map(|(i, f)| {
let icon = THEME let is_selected = self.folder.files.is_selected(f.path());
.icons
.iter()
.find(|x| x.name.match_path(f.url(), Some(f.is_dir())))
.map(|x| x.display.as_ref())
.unwrap_or("");
let is_selected = self.folder.files.is_selected(f.url());
if (!self.is_selection && is_selected) if (!self.is_selection && is_selected)
|| (self.is_selection && mode.pending(self.folder.offset() + i, is_selected)) || (self.is_selection && mode.pending(self.folder.offset() + i, is_selected))
{ {
@ -87,14 +99,35 @@ impl<'a> Widget for Folder<'a> {
self.file_style(f) self.file_style(f)
}; };
let mut path = format!(" {icon} {}", short_path(f.url(), &self.folder.cwd)); let mut spans = Vec::with_capacity(10);
spans.push(Span::raw(format!(" {} ", Self::icon(f))));
spans.push(Span::raw(readable_path(f.path(), &self.folder.cwd)));
if let Some(link_to) = f.link_to() { if let Some(link_to) = f.link_to() {
if MANAGER.show_symlink { if MANAGER.show_symlink {
path.push_str(&format!(" -> {}", link_to.display())); spans.push(Span::raw(format!(" -> {}", link_to.display())));
} }
} }
ListItem::new(path).style(style) if let Some(idx) = active
.finder()
.filter(|_| hovered && self.is_find)
.and_then(|finder| finder.matched_idx(f.path()))
{
let len = active.finder().unwrap().matched().len();
let style = Style::new().fg(Color::Rgb(255, 255, 50)).add_modifier(Modifier::ITALIC);
spans.push(Span::styled(
format!(
" [{}/{}]",
if idx > 99 { ">99".to_string() } else { (idx + 1).to_string() },
if len > 99 { ">99".to_string() } else { len.to_string() }
),
style,
));
}
ListItem::new(Line::from(spans)).style(style)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View file

@ -39,6 +39,7 @@ impl<'a> Widget for Layout<'a> {
// Current // Current
Folder::new(self.cx, manager.current()) Folder::new(self.cx, manager.current())
.with_selection(manager.active().mode().is_visual()) .with_selection(manager.active().mode().is_visual())
.with_find(manager.active().finder().is_some())
.render(chunks[1], buf); .render(chunks[1], buf);
// Preview // Preview

View file

@ -26,7 +26,7 @@ impl<'a> Widget for Left<'a> {
let separator = &THEME.status.separator; let separator = &THEME.status.separator;
// Mode // Mode
let mut spans = vec![]; let mut spans = Vec::with_capacity(5);
spans.push(Span::styled(&separator.opening, primary.fg())); spans.push(Span::styled(&separator.opening, primary.fg()));
spans.push(Span::styled( spans.push(Span::styled(
format!(" {mode} "), format!(" {mode} "),

View file

@ -65,7 +65,7 @@ impl<'a> Right<'a> {
impl Widget for Right<'_> { impl Widget for Right<'_> {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let manager = self.cx.manager.current(); let manager = self.cx.manager.current();
let mut spans = vec![]; let mut spans = Vec::with_capacity(20);
// Permissions // Permissions
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]

View file

@ -16,7 +16,7 @@ impl Widget for Side<'_> {
.cands .cands
.into_iter() .into_iter()
.map(|c| { .map(|c| {
let mut spans = vec![]; let mut spans = Vec::with_capacity(10);
// Keys // Keys
let keys = c.on[self.times..].iter().map(ToString::to_string).collect::<Vec<_>>(); let keys = c.on[self.times..].iter().map(ToString::to_string).collect::<Vec<_>>();

View file

@ -64,6 +64,12 @@ keymap = [
{ on = [ "c", "f" ], exec = "copy filename", desc = "Copy the name of the file" }, { on = [ "c", "f" ], exec = "copy filename", desc = "Copy the name of the file" },
{ on = [ "c", "n" ], exec = "copy name_without_ext", desc = "Copy the name of the file without the extension" }, { on = [ "c", "n" ], exec = "copy name_without_ext", desc = "Copy the name of the file without the extension" },
# Find
{ on = [ "/" ], exec = "find" },
{ on = [ "?" ], exec = "find --previous" },
{ on = [ "-" ], exec = "find_arrow" },
{ on = [ "=" ], exec = "find_arrow --previous" },
# Sorting # Sorting
{ on = [ ",", "a" ], exec = "sort alphabetical --dir_first", desc = "Sort alphabetically, directories first" }, { on = [ ",", "a" ], exec = "sort alphabetical --dir_first", desc = "Sort alphabetically, directories first" },
{ on = [ ",", "A" ], exec = "sort alphabetical --reverse --dir_first", desc = "Sort alphabetically, directories first (reverse)" }, { on = [ ",", "A" ], exec = "sort alphabetical --reverse --dir_first", desc = "Sort alphabetically, directories first (reverse)" },

View file

@ -12,6 +12,11 @@ pub struct Control {
pub desc: Option<String>, pub desc: Option<String>,
} }
impl Control {
#[inline]
pub fn to_call(&self) -> Vec<Exec> { self.exec.clone() }
}
impl Control { impl Control {
#[inline] #[inline]
pub fn on(&self) -> String { self.on.iter().map(ToString::to_string).collect() } pub fn on(&self) -> String { self.on.iter().map(ToString::to_string).collect() }
@ -28,7 +33,7 @@ impl Control {
#[inline] #[inline]
pub fn contains(&self, s: &str) -> bool { pub fn contains(&self, s: &str) -> bool {
self.desc.as_ref().map(|d| d.contains(s)).unwrap_or(false) self.desc.as_ref().map(|d| d.contains(s)) == Some(true)
|| self.exec().contains(s) || self.exec().contains(s)
|| self.on().contains(s) || self.on().contains(s)
} }

View file

@ -86,3 +86,15 @@ impl Exec {
deserializer.deserialize_any(ExecVisitor) deserializer.deserialize_any(ExecVisitor)
} }
} }
impl Exec {
#[inline]
pub fn call(cwd: &str, args: Vec<String>) -> Vec<Self> {
vec![Exec { cmd: cwd.to_owned(), args, named: Default::default() }]
}
#[inline]
pub fn call_named(cwd: &str, named: BTreeMap<String, String>) -> Vec<Self> {
vec![Exec { cmd: cwd.to_owned(), args: Default::default(), named }]
}
}

View file

@ -1,7 +1,7 @@
use std::{collections::BTreeMap, ffi::OsString}; use std::{collections::BTreeMap, ffi::OsString};
use anyhow::Result; use anyhow::Result;
use config::{keymap::{Control, KeymapLayer}, open::Opener}; use config::{keymap::{Exec, KeymapLayer}, open::Opener};
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use shared::{InputError, RoCell, Url}; use shared::{InputError, RoCell, Url};
use tokio::sync::{mpsc::{self, UnboundedSender}, oneshot}; use tokio::sync::{mpsc::{self, UnboundedSender}, oneshot};
@ -18,7 +18,7 @@ pub enum Event {
Render(String), Render(String),
Resize(u16, u16), Resize(u16, u16),
Stop(bool, Option<oneshot::Sender<()>>), Stop(bool, Option<oneshot::Sender<()>>),
Ctrl(Control, KeymapLayer), Call(Vec<Exec>, KeymapLayer),
// Manager // Manager
Cd(Url), Cd(Url),
@ -67,8 +67,8 @@ macro_rules! emit {
let (tx, rx) = tokio::sync::oneshot::channel(); let (tx, rx) = tokio::sync::oneshot::channel();
$crate::Event::Stop($state, Some(tx)).wait(rx) $crate::Event::Stop($state, Some(tx)).wait(rx)
}}; }};
(Ctrl($exec:expr, $layer:expr)) => { (Call($exec:expr, $layer:expr)) => {
$crate::Event::Ctrl($exec, $layer).emit(); $crate::Event::Call($exec, $layer).emit();
}; };
(Cd($url:expr)) => { (Cd($url:expr)) => {

View file

@ -0,0 +1,91 @@
use std::{collections::BTreeMap, ffi::OsStr, path::{Path, PathBuf}};
use anyhow::Result;
use regex::bytes::Regex;
use crate::files::Files;
pub struct Finder {
query: Regex,
matched: BTreeMap<PathBuf, u8>,
version: usize,
}
impl Finder {
pub(super) fn new(s: &str) -> Result<Self> {
Ok(Self { query: Regex::new(s)?, matched: Default::default(), version: 0 })
}
pub(super) fn arrow(&self, files: &Files, cursor: usize, prev: bool) -> Option<isize> {
if prev {
files
.iter()
.take(cursor)
.rev()
.enumerate()
.find(|(_, f)| f.name().map_or(false, |n| self.is_match(n)))
.map(|(i, _)| -(i as isize) - 1)
} else {
files
.iter()
.skip(cursor + 1)
.enumerate()
.find(|(_, f)| f.name().map_or(false, |n| self.is_match(n)))
.map(|(i, _)| i as isize + 1)
}
}
pub(super) fn catchup(&mut self, files: &Files) -> bool {
if self.version == files.version() {
return false;
}
self.matched.clear();
let mut i = 0u8;
for file in files.iter() {
if file.name().map(|n| self.is_match(n)) != Some(true) {
continue;
}
self.matched.insert(file.path_owned(), i);
if self.matched.len() > 99 {
break;
}
i += 1;
}
self.version = files.version();
true
}
#[inline]
fn is_match(&self, name: &OsStr) -> bool {
#[cfg(target_os = "windows")]
{
self.query.is_match(name.to_string_lossy().as_bytes())
}
#[cfg(not(target_os = "windows"))]
{
use std::os::unix::ffi::OsStrExt;
self.query.is_match(name.as_bytes())
}
}
}
impl Finder {
#[inline]
pub fn matched(&self) -> &BTreeMap<PathBuf, u8> { &self.matched }
#[inline]
pub fn matched_idx(&self, path: &Path) -> Option<u8> {
if let Some((_, &idx)) = self.matched.iter().find(|(p, _)| *p == path) {
return Some(idx);
}
if path.file_name().map(|n| self.is_match(n)) == Some(true) {
return Some(100);
}
None
}
}

View file

@ -1,3 +1,4 @@
mod finder;
mod folder; mod folder;
mod manager; mod manager;
mod mode; mod mode;
@ -6,6 +7,7 @@ mod tab;
mod tabs; mod tabs;
mod watcher; mod watcher;
pub use finder::*;
pub use folder::*; pub use folder::*;
pub use manager::*; pub use manager::*;
pub use mode::*; pub use mode::*;

View file

@ -38,10 +38,10 @@ impl Which {
self.switch(false); self.switch(false);
} else if self.cands.len() == 1 { } else if self.cands.len() == 1 {
self.switch(false); self.switch(false);
emit!(Ctrl(self.cands.remove(0), self.layer)); emit!(Call(self.cands[0].to_call(), self.layer));
} else if let Some(i) = self.cands.iter().position(|c| c.on.len() == self.times + 1) { } else if let Some(i) = self.cands.iter().position(|c| c.on.len() == self.times + 1) {
self.switch(false); self.switch(false);
emit!(Ctrl(self.cands.remove(i), self.layer)); emit!(Call(self.cands[i].to_call(), self.layer));
} }
self.times += 1; self.times += 1;