diff --git a/app/src/manager/folder.rs b/app/src/manager/folder.rs index 68bafe86..dd1743c4 100644 --- a/app/src/manager/folder.rs +++ b/app/src/manager/folder.rs @@ -2,27 +2,32 @@ use core::files::File; use config::{MANAGER, THEME}; use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{List, ListItem, Widget}}; -use shared::short_path; +use shared::{short_path, short_path_parts, PathParts}; use crate::Ctx; pub(super) struct Folder<'a> { cx: &'a Ctx, folder: &'a core::manager::Folder, - is_preview: bool, + location: FolderLocation, is_selection: bool, is_find: bool, } -impl<'a> Folder<'a> { - pub(super) fn new(cx: &'a Ctx, folder: &'a core::manager::Folder) -> Self { - Self { cx, folder, is_preview: false, is_selection: false, is_find: false } - } +#[derive(Copy, Clone, Eq, PartialEq)] +pub(super) enum FolderLocation { + Parent, + Current, + Preview, +} - #[inline] - pub(super) fn with_preview(mut self, state: bool) -> Self { - self.is_preview = state; - self +impl<'a> Folder<'a> { + pub(super) fn new( + cx: &'a Ctx, + folder: &'a core::manager::Folder, + location: FolderLocation, + ) -> Self { + Self { cx, folder, location, is_selection: false, is_find: false } } #[inline] @@ -66,7 +71,10 @@ impl<'a> Widget for Folder<'a> { let active = self.cx.manager.active(); let mode = active.mode(); - let window = if self.is_preview { + // TODO to be configured by THEME? + let find_style = Style::new().fg(Color::Rgb(255, 255, 50)).add_modifier(Modifier::ITALIC); + + let window = if self.location == FolderLocation::Preview { self.folder.window_for(active.preview().skip()) } else { self.folder.window() @@ -91,7 +99,7 @@ impl<'a> Widget for Folder<'a> { } let hovered = matches!(self.folder.hovered, Some(ref h) if h.url() == f.url()); - let style = if self.is_preview && hovered { + let style = if self.location == FolderLocation::Preview && hovered { THEME.preview.hovered.get() } else if hovered { THEME.selection.hovered.get() @@ -102,7 +110,24 @@ impl<'a> Widget for Folder<'a> { let mut spans = Vec::with_capacity(10); spans.push(Span::raw(format!(" {} ", Self::icon(f)))); - spans.push(Span::raw(short_path(f.url(), &self.folder.cwd))); + if let Some((path, (n_prefix, n_hl, n_suffix))) = + // If this is the current folder, + (self.location == FolderLocation::Current).then_some(()).and_then(|_| { + // ... and we are now finding something, + let finder = active.finder()?; + // ... and we can get the short path parts, + let PathParts { path, filename } = short_path_parts(f.url(), &self.folder.cwd)?; + // ... and we can get the highlight range, + Some((path, finder.try_render_highlight(filename)?)) + }) { + // ... then render the highlighted short path. + spans.push(Span::raw(path.join(n_prefix).display().to_string())); + spans.push(Span::styled(n_hl, find_style)); + spans.push(Span::raw(n_suffix)); + } else { + // Otherwise, just render the short path. + spans.push(Span::raw(short_path(f.url(), &self.folder.cwd))); + } if let Some(link_to) = f.link_to() { if MANAGER.show_symlink { @@ -110,21 +135,22 @@ impl<'a> Widget for Folder<'a> { } } - if let Some(idx) = active - .finder() - .filter(|&f| hovered && self.is_find && f.has_matched()) - .and_then(|finder| finder.matched_idx(f.url())) - { - 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, - )); + if hovered && self.is_find { + if let Some(idx) = active + .finder() + .filter(|&f| f.has_matched()) + .and_then(|finder| finder.matched_idx(f.url())) + { + let len = active.finder().unwrap().matched().len(); + 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() } + ), + find_style, + )); + } } ListItem::new(Line::from(spans)).style(style) diff --git a/app/src/manager/layout.rs b/app/src/manager/layout.rs index 8aeb6f13..84f6ac72 100644 --- a/app/src/manager/layout.rs +++ b/app/src/manager/layout.rs @@ -2,7 +2,7 @@ use config::MANAGER; use ratatui::{buffer::Buffer, layout::{self, Constraint, Direction, Rect}, widgets::{Block, Borders, Padding, Widget}}; use super::{Folder, Preview}; -use crate::Ctx; +use crate::{manager::folder::FolderLocation, Ctx}; pub(crate) struct Layout<'a> { cx: &'a Ctx, @@ -32,12 +32,12 @@ impl<'a> Widget for Layout<'a> { // Parent let block = Block::new().borders(Borders::RIGHT).padding(Padding::new(1, 0, 0, 0)); if let Some(parent) = manager.parent() { - Folder::new(self.cx, parent).render(block.inner(chunks[0]), buf); + Folder::new(self.cx, parent, FolderLocation::Parent).render(block.inner(chunks[0]), buf); } block.render(chunks[0], buf); // Current - Folder::new(self.cx, manager.current()) + Folder::new(self.cx, manager.current(), FolderLocation::Current) .with_selection(manager.active().mode().is_visual()) .with_find(manager.active().finder().is_some()) .render(chunks[1], buf); diff --git a/app/src/manager/preview.rs b/app/src/manager/preview.rs index c75df174..fc4ced3a 100644 --- a/app/src/manager/preview.rs +++ b/app/src/manager/preview.rs @@ -4,7 +4,7 @@ use ansi_to_tui::IntoText; use ratatui::{buffer::Buffer, layout::Rect, widgets::{Paragraph, Widget}}; use super::Folder; -use crate::Ctx; +use crate::{manager::folder::FolderLocation, Ctx}; pub(super) struct Preview<'a> { cx: &'a Ctx, @@ -29,7 +29,7 @@ impl<'a> Widget for Preview<'a> { match &preview.lock.as_ref().unwrap().data { PreviewData::Folder => { if let Some(folder) = manager.active().history(hovered) { - Folder::new(self.cx, folder).with_preview(true).render(area, buf); + Folder::new(self.cx, folder, FolderLocation::Preview).render(area, buf); } } PreviewData::Text(s) => { diff --git a/core/src/manager/finder.rs b/core/src/manager/finder.rs index 01a6b85a..3f0674ba 100644 --- a/core/src/manager/finder.rs +++ b/core/src/manager/finder.rs @@ -108,6 +108,29 @@ impl Finder { self.query.is_match(name.as_bytes()) } } + + /// Try to render the highlight range for the given name. + /// + /// # Returns + /// (prefix, highlight, suffix) + pub fn try_render_highlight(&self, name: &OsStr) -> Option<(String, String, String)> { + let name_bytes; + #[cfg(target_os = "windows")] + { + name_bytes = name.to_string_lossy().as_bytes(); + } + #[cfg(not(target_os = "windows"))] + { + use std::os::unix::ffi::OsStrExt; + name_bytes = name.as_bytes(); + } + let range = self.query.find(name_bytes).map(|m| m.range())?; + Some(( + String::from_utf8_lossy(&name_bytes[..range.start]).into_owned(), + String::from_utf8_lossy(&name_bytes[range.start..range.end]).into_owned(), + String::from_utf8_lossy(&name_bytes[range.end..]).into_owned(), + )) + } } impl Finder { diff --git a/shared/src/fns.rs b/shared/src/fns.rs index be1daa28..9f5cd6f1 100644 --- a/shared/src/fns.rs +++ b/shared/src/fns.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, env, path::{Component, Path, PathBuf}}; +use std::{env, ffi::OsStr, path::{Component, Path, PathBuf}}; use tokio::fs; @@ -23,6 +23,22 @@ pub fn expand_url(mut u: Url) -> Url { u } +pub struct PathParts<'a> { + pub path: &'a Path, + pub filename: &'a OsStr, +} + +pub fn short_path_parts<'a>(p: &'a Path, base: &Path) -> Option> { + let p = p.strip_prefix(base).unwrap_or(p); + let mut parts = p.components(); + let filename = parts.next_back().and_then(|p| match p { + Component::Normal(p) => Some(p), + _ => None, + })?; + let rest = parts.as_path(); + Some(PathParts { path: rest, filename }) +} + pub fn short_path(p: &Path, base: &Path) -> String { if let Ok(p) = p.strip_prefix(base) { return p.display().to_string();