This commit is contained in:
sxyazi 2023-09-24 11:45:08 +08:00
parent 749b1e8cce
commit 6bdb7b9e04
No known key found for this signature in database
5 changed files with 79 additions and 95 deletions

View file

@ -2,32 +2,21 @@ 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, short_path_parts, PathParts};
use shared::short_path;
use crate::Ctx;
pub(super) struct Folder<'a> {
cx: &'a Ctx,
folder: &'a core::manager::Folder,
location: FolderLocation,
is_preview: bool,
is_selection: bool,
is_find: bool,
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub(super) enum FolderLocation {
Parent,
Current,
Preview,
}
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 }
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 }
}
#[inline]
@ -55,7 +44,7 @@ impl<'a> Folder<'a> {
}
#[inline]
fn file_style(&self, file: &File) -> Style {
fn hovered_style(&self, file: &File) -> Style {
let mimetype = &self.cx.manager.mimetype;
THEME
.filetypes
@ -64,6 +53,25 @@ impl<'a> Folder<'a> {
.map(|x| x.style.get())
.unwrap_or_else(Style::new)
}
fn highlighted_style<'b>(&'b self, file: &'b File) -> Vec<Span> {
let short = short_path(file.url(), &self.folder.cwd);
let v = self.is_find.then_some(()).and_then(|_| {
let finder = self.cx.manager.active().finder()?;
let (head, body, tail) = finder.explode(short.name)?;
// TODO: to be configured by THEME?
let style = Style::new().fg(Color::Rgb(255, 255, 50)).add_modifier(Modifier::ITALIC);
Some(vec![
Span::raw(short.prefix.join(head.as_ref()).display().to_string()),
Span::styled(body, style),
Span::raw(tail),
])
});
v.unwrap_or_else(|| vec![Span::raw(format!("{}", short))])
}
}
impl<'a> Widget for Folder<'a> {
@ -71,10 +79,7 @@ impl<'a> Widget for Folder<'a> {
let active = self.cx.manager.active();
let mode = active.mode();
// 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 {
let window = if self.is_preview {
self.folder.window_for(active.preview().skip())
} else {
self.folder.window()
@ -99,35 +104,17 @@ impl<'a> Widget for Folder<'a> {
}
let hovered = matches!(self.folder.hovered, Some(ref h) if h.url() == f.url());
let style = if self.location == FolderLocation::Preview && hovered {
let style = if self.is_preview && hovered {
THEME.preview.hovered.get()
} else if hovered {
THEME.selection.hovered.get()
} else {
self.file_style(f)
self.hovered_style(f)
};
let mut spans = Vec::with_capacity(10);
spans.push(Span::raw(format!(" {} ", Self::icon(f))));
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)));
}
spans.extend(self.highlighted_style(f));
if let Some(link_to) = f.link_to() {
if MANAGER.show_symlink {
@ -135,22 +122,21 @@ impl<'a> Widget for Folder<'a> {
}
}
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,
));
}
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();
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() }
),
// TODO: to be configured by THEME?
Style::new().fg(Color::Rgb(255, 255, 50)).add_modifier(Modifier::ITALIC),
));
}
ListItem::new(Line::from(spans)).style(style)

View file

@ -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::{manager::folder::FolderLocation, Ctx};
use crate::context::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, FolderLocation::Parent).render(block.inner(chunks[0]), buf);
Folder::new(self.cx, parent).render(block.inner(chunks[0]), buf);
}
block.render(chunks[0], buf);
// Current
Folder::new(self.cx, manager.current(), FolderLocation::Current)
Folder::new(self.cx, manager.current())
.with_selection(manager.active().mode().is_visual())
.with_find(manager.active().finder().is_some())
.render(chunks[1], buf);

View file

@ -4,7 +4,7 @@ use ansi_to_tui::IntoText;
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Paragraph, Widget}};
use super::Folder;
use crate::{manager::folder::FolderLocation, Ctx};
use crate::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, FolderLocation::Preview).render(area, buf);
Folder::new(self.cx, folder).render(area, buf);
}
}
PreviewData::Text(s) => {

View file

@ -1,4 +1,4 @@
use std::{collections::BTreeMap, ffi::OsStr};
use std::{borrow::Cow, collections::BTreeMap, ffi::OsStr};
use anyhow::Result;
use regex::bytes::Regex;
@ -109,26 +109,23 @@ impl Finder {
}
}
/// 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;
/// Explode the name into three parts: head, body, tail.
#[inline]
pub fn explode<'a>(&self, name: &'a OsStr) -> Option<(Cow<'a, str>, Cow<'a, str>, Cow<'a, str>)> {
#[cfg(target_os = "windows")]
{
name_bytes = name.to_string_lossy().as_bytes();
}
let b = { name.to_string_lossy().as_bytes() };
#[cfg(not(target_os = "windows"))]
{
let b = {
use std::os::unix::ffi::OsStrExt;
name_bytes = name.as_bytes();
}
let range = self.query.find(name_bytes).map(|m| m.range())?;
name.as_bytes()
};
let range = self.query.find(b).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(),
String::from_utf8_lossy(&b[..range.start]),
String::from_utf8_lossy(&b[range.start..range.end]),
String::from_utf8_lossy(&b[range.end..]),
))
}
}

View file

@ -1,4 +1,4 @@
use std::{env, ffi::OsStr, path::{Component, Path, PathBuf}};
use std::{env, ffi::OsStr, fmt::Display, path::{Component, Path, PathBuf}};
use tokio::fs;
@ -23,27 +23,28 @@ pub fn expand_url(mut u: Url) -> Url {
u
}
pub struct PathParts<'a> {
pub path: &'a Path,
pub filename: &'a OsStr,
pub struct ShortPath<'a> {
pub prefix: &'a Path,
pub name: &'a OsStr,
}
pub fn short_path_parts<'a>(p: &'a Path, base: &Path) -> Option<PathParts<'a>> {
impl Display for ShortPath<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.prefix == Path::new("") {
return write!(f, "{}", self.name.to_string_lossy());
}
write!(f, "{}/{}", self.prefix.display(), self.name.to_string_lossy())
}
}
pub fn short_path<'a>(p: &'a Path, base: &Path) -> ShortPath<'a> {
let p = p.strip_prefix(base).unwrap_or(p);
let mut parts = p.components();
let filename = parts.next_back().and_then(|p| match p {
let name = 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();
}
p.display().to_string()
});
ShortPath { prefix: parts.as_path(), name: name.unwrap_or_default() }
}
pub fn readable_path(p: &Path) -> String {