This commit is contained in:
sxyazi 2023-10-11 18:59:47 +08:00
parent e2dfa9813a
commit ebe6714ef6
No known key found for this signature in database
6 changed files with 149 additions and 193 deletions

View file

@ -15,42 +15,11 @@ impl Folder {
pub(super) fn new(kind: FolderKind) -> Self { Self { kind } } pub(super) fn new(kind: FolderKind) -> Self { Self { kind } }
} }
impl Folder {
// let short = short_path(file.url(), &self.folder.cwd);
}
impl Widget for Folder { impl Widget for Folder {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let x = plugin::Folder { kind: self.kind as u8 }.render(area, buf); let x = plugin::Folder { kind: self.kind as u8 }.render(area, buf);
if x.is_err() { if x.is_err() {
info!("{:?}", x); info!("{:?}", x);
} }
// let items: Vec<_> = window
// .iter()
// .enumerate()
// .map(|(i, f)| {
// 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)
// })
// .collect();
// List::new(items).render(area, buf);
} }
} }

View file

@ -91,7 +91,7 @@ impl Finder {
/// Explode the name into three parts: head, body, tail. /// Explode the name into three parts: head, body, tail.
#[inline] #[inline]
pub fn highlighted(&self, name: &OsStr) -> Vec<Range<usize>> { pub fn highlighted(&self, name: &OsStr) -> Option<Vec<Range<usize>>> {
#[cfg(windows)] #[cfg(windows)]
let found = self.query.find(name.to_string_lossy().as_bytes()).map(|m| m.range()); let found = self.query.find(name.to_string_lossy().as_bytes()).map(|m| m.range());
@ -101,7 +101,7 @@ impl Finder {
self.query.find(name.as_bytes()).map(|m| m.range()) self.query.find(name.as_bytes()).map(|m| m.range())
}; };
found.map(|r| vec![r]).unwrap_or_default() found.map(|r| vec![r])
} }
} }
@ -109,17 +109,11 @@ impl Finder {
#[inline] #[inline]
pub fn matched(&self) -> &BTreeMap<Url, u8> { &self.matched } pub fn matched(&self) -> &BTreeMap<Url, u8> { &self.matched }
#[inline]
pub fn has_matched(&self) -> bool { !self.matched.is_empty() }
#[inline] #[inline]
pub fn matched_idx(&self, url: &Url) -> Option<u8> { pub fn matched_idx(&self, url: &Url) -> Option<u8> {
if let Some((_, &idx)) = self.matched.iter().find(|(u, _)| *u == url) { if let Some((_, &idx)) = self.matched.iter().find(|(u, _)| *u == url) {
return Some(idx); return Some(idx);
} }
if url.file_name().map(|n| self.matches(n)) == Some(true) {
return Some(100);
}
None None
} }
} }

View file

@ -57,6 +57,33 @@ function Folder:markers(area, markers)
return elements return elements
end end
function Folder:highlighted_name(file)
-- Complete prefix when searching across directories
local prefix = file:prefix() or ""
if prefix ~= "" then
prefix = prefix .. "/"
end
-- Range highlighting for filenames
local highlights = file:highlights()
local spans = ui.highlight_ranges(prefix .. file.name, highlights)
-- Show symlink target
if MANAGER.show_symlink and file.link_to ~= nil then
spans[#spans + 1] = ui.Span(" -> " .. tostring(file.link_to)):italic()
end
if highlights == nil or not file:is_hovered() then
return spans
end
local found = file:found()
if found ~= nil then
spans[#spans + 1] = ui.Span(string.format(" [%d/%d]", found[1] + 1, found[2])):fg("#ffff32"):italic()
end
return spans
end
function Folder:parent(area) function Folder:parent(area)
local folder = self:by_kind(self.Kind.Parent) local folder = self:by_kind(self.Kind.Parent)
if folder == nil then if folder == nil then
@ -66,7 +93,7 @@ function Folder:parent(area)
local items = {} local items = {}
for _, f in ipairs(folder.window) do for _, f in ipairs(folder.window) do
local item = ui.ListItem(" " .. f:icon() .. " " .. f.name .. " ") local item = ui.ListItem(" " .. f:icon() .. " " .. f.name .. " ")
if f.hovered then if f:is_hovered() then
item = item:style(THEME.files.hovered) item = item:style(THEME.files.hovered)
else else
item = item:style(f:style()) item = item:style(f:style())
@ -82,18 +109,11 @@ function Folder:current(area)
local markers = {} local markers = {}
local items = {} local items = {}
for i, f in ipairs(self:by_kind(self.Kind.Current).window) do for i, f in ipairs(self:by_kind(self.Kind.Current).window) do
local name = ui.highlight_ranges(f.name, f:highlights()) local name = self:highlighted_name(f)
-- Show symlink target
if MANAGER.show_symlink then
if f.link_to ~= nil then
name[#name + 1] = ui.Span(" -> " .. tostring(f.link_to)):italic()
end
end
-- Highlight hovered file -- Highlight hovered file
local item = ui.ListItem(ui.Line { ui.Span(" " .. f:icon() .. " "), table.unpack(name) }) local item = ui.ListItem(ui.Line { ui.Span(" " .. f:icon() .. " "), table.unpack(name) })
if f.hovered then if f:is_hovered() then
item = item:style(THEME.files.hovered) item = item:style(THEME.files.hovered)
else else
item = item:style(f:style()) item = item:style(f:style())
@ -101,10 +121,10 @@ function Folder:current(area)
items[#items + 1] = item items[#items + 1] = item
-- Mark yanked/selected files -- Mark yanked/selected files
local yanked = f:yanked() local yanked = f:is_yanked()
if yanked ~= 0 then if yanked ~= 0 then
markers[#markers + 1] = { i, yanked } markers[#markers + 1] = { i, yanked }
elseif f:selected() then elseif f:is_selected() then
markers[#markers + 1] = { i, 3 } markers[#markers + 1] = { i, 3 }
end end
end end
@ -121,7 +141,7 @@ function Folder:preview(area)
local items = {} local items = {}
for _, f in ipairs(folder.window) do for _, f in ipairs(folder.window) do
local item = ui.ListItem(" " .. f:icon() .. " " .. f.name .. " ") local item = ui.ListItem(" " .. f:icon() .. " " .. f.name .. " ")
if f.hovered then if f:is_hovered() then
item = item:style(THEME.preview.hovered) item = item:style(THEME.preview.hovered)
else else
item = item:style(f:style()) item = item:style(f:style())

View file

@ -1,10 +1,10 @@
use core::Ctx; use core::Ctx;
use config::{MANAGER, THEME}; use config::MANAGER;
use mlua::{AnyUserData, MetaMethod, UserDataFields, UserDataMethods, Value}; use mlua::{AnyUserData, MetaMethod, UserDataFields, UserDataMethods, Value};
use super::{Range, Url}; use super::Url;
use crate::{layout::Style, LUA}; use crate::LUA;
pub struct Active<'a, 'b> { pub struct Active<'a, 'b> {
scope: &'b mlua::Scope<'a, 'a>, scope: &'b mlua::Scope<'a, 'a>,
@ -99,80 +99,8 @@ impl<'a, 'b> Active<'a, 'b> {
) -> mlua::Result<AnyUserData<'a>> { ) -> mlua::Result<AnyUserData<'a>> {
let ud = self.scope.create_any_userdata_ref(inner)?; let ud = self.scope.create_any_userdata_ref(inner)?;
ud.set_named_user_value("idx", idx)?; ud.set_named_user_value("idx", idx)?;
ud.set_named_user_value("folder", self.scope.create_any_userdata_ref(folder)?)?;
ud.set_named_user_value( ud.set_named_user_value("manager", self.scope.create_any_userdata_ref(&self.cx.manager)?)?;
"icon",
self.scope.create_function(|_, ()| {
Ok(
THEME
.icons
.iter()
.find(|&x| x.name.match_path(inner.url(), Some(inner.is_dir())))
.map(|x| x.display.to_string()),
)
})?,
)?;
ud.set_named_user_value(
"style",
self.scope.create_function(|_, ()| {
let mime = self.cx.manager.mimetype.get(inner.url());
Ok(
THEME
.filetypes
.iter()
.find(|&x| x.matches(inner.url(), mime, inner.is_dir()))
.map(|x| Style::from(x.style)),
)
})?,
)?;
ud.set_named_user_value(
"hovered",
matches!(&folder.hovered, Some(f) if f.url() == inner.url()),
)?;
ud.set_named_user_value(
"yanked",
self.scope.create_function(|_, ()| {
let (cut, urls) = self.cx.manager.yanked();
Ok(if !urls.contains(inner.url()) {
0u8
} else if *cut {
2u8
} else {
1u8
})
})?,
)?;
ud.set_named_user_value(
"selected",
self.scope.create_function(|_, me: AnyUserData| {
let is_visual = self.inner.mode.is_visual();
let selected = folder.files.is_selected(inner.url());
Ok(if !is_visual {
selected
} else {
let idx: usize = me.named_user_value("idx")?;
self.inner.mode.pending(folder.offset() + idx, selected)
})
})?,
)?;
ud.set_named_user_value(
"highlights",
self.scope.create_function(|_, ()| {
let Some(finder) = self.inner.finder() else {
return Ok(None);
};
Ok(
inner
.name()
.map(|n| finder.highlighted(n).into_iter().map(Range::from).collect::<Vec<_>>()),
)
})?,
)?;
Ok(ud) Ok(ud)
} }

View file

@ -1,6 +1,7 @@
use mlua::{AnyUserData, Function, IntoLua, MetaMethod, UserData, UserDataFields, UserDataMethods, Value}; use config::THEME;
use mlua::{AnyUserData, IntoLua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef};
use super::Url; use super::{Range, Url};
use crate::{layout::Style, LUA}; use crate::{layout::Style, LUA};
pub struct File(core::files::File); pub struct File(core::files::File);
@ -46,36 +47,115 @@ impl Files {
})?; })?;
LUA.register_userdata_type::<core::files::File>(|reg| { LUA.register_userdata_type::<core::files::File>(|reg| {
reg.add_field_method_get("name", |_, me| {
Ok(me.url().file_name().map(|n| n.to_string_lossy().to_string()))
});
reg.add_function("icon", |_, me: AnyUserData| {
me.named_user_value::<Function>("icon")?.call::<_, String>(())
});
reg.add_function("style", |_, me: AnyUserData| {
me.named_user_value::<Function>("style")?.call::<_, Style>(())
});
reg.add_field_function_get("hovered", |_, me| me.named_user_value::<bool>("hovered"));
reg.add_function("yanked", |_, me: AnyUserData| {
me.named_user_value::<Function>("yanked")?.call::<_, u8>(me)
});
reg.add_function("selected", |_, me: AnyUserData| {
me.named_user_value::<Function>("selected")?.call::<_, bool>(me)
});
reg.add_function("highlights", |_, me: AnyUserData| {
me.named_user_value::<Function>("highlights")?.call::<_, Value>(())
});
reg.add_field_method_get("url", |_, me| Ok(Url::from(me.url()))); reg.add_field_method_get("url", |_, me| Ok(Url::from(me.url())));
reg.add_field_method_get("length", |_, me| Ok(me.length())); reg.add_field_method_get("length", |_, me| Ok(me.length()));
reg.add_field_method_get("link_to", |_, me| Ok(me.link_to().map(Url::from))); reg.add_field_method_get("link_to", |_, me| Ok(me.link_to().map(Url::from)));
reg.add_field_method_get("is_link", |_, me| Ok(me.is_link())); reg.add_field_method_get("is_link", |_, me| Ok(me.is_link()));
reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden())); reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden()));
// Meta reg.add_field_method_get("name", |_, me| {
Ok(me.url().file_name().map(|n| n.to_string_lossy().to_string()))
});
reg.add_field_method_get("permissions", |_, me| { reg.add_field_method_get("permissions", |_, me| {
Ok(shared::permissions(me.meta().permissions())) Ok(shared::permissions(me.meta().permissions()))
}); });
reg.add_function("prefix", |_, me: AnyUserData| {
let folder = me.named_user_value::<UserDataRef<core::manager::Folder>>("folder")?;
if !folder.cwd.is_search() {
return Ok(None);
}
let file = me.borrow::<core::files::File>()?;
let mut p = file.url().strip_prefix(&folder.cwd).unwrap_or(file.url()).components();
p.next_back();
Ok(Some(p.as_path().to_string_lossy().to_string()))
});
reg.add_method("icon", |_, me, ()| {
Ok(
THEME
.icons
.iter()
.find(|&x| x.name.match_path(me.url(), Some(me.is_dir())))
.map(|x| x.display.to_string()),
)
});
reg.add_function("style", |_, me: AnyUserData| {
let manager = me.named_user_value::<UserDataRef<core::manager::Manager>>("manager")?;
let file = me.borrow::<core::files::File>()?;
let mime = manager.mimetype.get(file.url());
Ok(
THEME
.filetypes
.iter()
.find(|&x| x.matches(file.url(), mime, file.is_dir()))
.map(|x| Style::from(x.style)),
)
});
reg.add_function("is_hovered", |_, me: AnyUserData| {
let folder = me.named_user_value::<UserDataRef<core::manager::Folder>>("folder")?;
let file = me.borrow::<core::files::File>()?;
Ok(matches!(&folder.hovered, Some(f) if f.url() == file.url()))
});
reg.add_function("is_yanked", |_, me: AnyUserData| {
let manager = me.named_user_value::<UserDataRef<core::manager::Manager>>("manager")?;
let file = me.borrow::<core::files::File>()?;
Ok(if !manager.yanked().1.contains(file.url()) {
0u8
} else if manager.yanked().0 {
2u8
} else {
1u8
})
});
reg.add_function("is_selected", |_, me: AnyUserData| {
let manager = me.named_user_value::<UserDataRef<core::manager::Manager>>("manager")?;
let folder = me.named_user_value::<UserDataRef<core::manager::Folder>>("folder")?;
let file = me.borrow::<core::files::File>()?;
let selected = folder.files.is_selected(file.url());
Ok(if !manager.active().mode.is_visual() {
selected
} else {
let idx: usize = me.named_user_value("idx")?;
manager.active().mode.pending(folder.offset() + idx, selected)
})
});
reg.add_function("found", |lua, me: AnyUserData| {
let manager = me.named_user_value::<UserDataRef<core::manager::Manager>>("manager")?;
let Some(finder) = manager.active().finder() else {
return Ok(None);
};
let file = me.borrow::<core::files::File>()?;
if let Some(idx) = finder.matched_idx(file.url()) {
return Some(
lua.create_sequence_from([idx.into_lua(lua)?, finder.matched().len().into_lua(lua)?]),
)
.transpose();
}
Ok(None)
});
reg.add_function("highlights", |_, me: AnyUserData| {
let manager = me.named_user_value::<UserDataRef<core::manager::Manager>>("manager")?;
let Some(finder) = manager.active().finder() else {
return Ok(None);
};
let file = me.borrow::<core::files::File>()?;
let Some(h) = file.name().and_then(|n| finder.highlighted(n)) else {
return Ok(None);
};
Ok(Some(h.into_iter().map(Range::from).collect::<Vec<_>>()))
});
})?; })?;
Ok(()) Ok(())

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, env, ffi::OsStr, fmt::Display, path::{Component, Path, PathBuf}}; use std::{borrow::Cow, env, path::{Component, Path, PathBuf}};
use tokio::fs; use tokio::fs;
@ -43,39 +43,6 @@ pub fn expand_url(mut u: Url) -> Url {
u u
} }
pub struct ShortPath<'a> {
pub prefix: &'a Path,
pub name: &'a OsStr,
}
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 name = parts.next_back().and_then(|p| match p {
Component::Normal(p) => Some(p),
_ => None,
});
ShortPath { prefix: parts.as_path(), name: name.unwrap_or_default() }
}
pub fn readable_path(p: &Path) -> String {
if let Some(home) = env::var_os("HOME") {
if let Ok(p) = p.strip_prefix(home) {
return format!("~/{}", p.display());
}
}
p.display().to_string()
}
pub async fn unique_path(mut p: Url) -> Url { pub async fn unique_path(mut p: Url) -> Url {
let Some(name) = p.file_name().map(|n| n.to_owned()) else { let Some(name) = p.file_name().map(|n| n.to_owned()) else {
return p; return p;
@ -91,7 +58,7 @@ pub async fn unique_path(mut p: Url) -> Url {
p p
} }
// Parmaters // Parameters
// * `path`: The absolute path(contains no `/./`) to get relative path. // * `path`: The absolute path(contains no `/./`) to get relative path.
// * `root`: The absolute path(contains no `/./`) to be compared. // * `root`: The absolute path(contains no `/./`) to be compared.
// //
@ -138,12 +105,10 @@ pub fn path_relative_to<'a>(path: &'a Path, root: &Path) -> Cow<'a, Path> {
#[inline] #[inline]
pub fn optional_bool(s: &str) -> Option<bool> { pub fn optional_bool(s: &str) -> Option<bool> {
if s == "true" { match s {
Some(true) "true" => Some(true),
} else if s == "false" { "false" => Some(false),
Some(false) _ => None,
} else {
None
} }
} }