mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: dedicated mime fetcher, previewer, and spotter for VFS (#3203)
This commit is contained in:
parent
6b09f2b665
commit
d905cbf2d7
12 changed files with 83 additions and 51 deletions
10
.github/workflows/draft.yml
vendored
10
.github/workflows/draft.yml
vendored
|
|
@ -45,8 +45,9 @@ jobs:
|
||||||
if: matrix.gcc != ''
|
if: matrix.gcc != ''
|
||||||
run: sudo apt update && sudo apt install -yq ${{ matrix.gcc }}
|
run: sudo apt update && sudo apt install -yq ${{ matrix.gcc }}
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
run: rustup toolchain install stable --profile minimal --target ${{ matrix.target }}
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.9
|
||||||
|
|
@ -79,8 +80,9 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
run: rustup toolchain install stable --profile minimal --target ${{ matrix.target }}
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.9
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ pub(super) struct File {
|
||||||
v_cha: Option<Value>,
|
v_cha: Option<Value>,
|
||||||
v_url: Option<Value>,
|
v_url: Option<Value>,
|
||||||
v_link_to: Option<Value>,
|
v_link_to: Option<Value>,
|
||||||
v_name: Option<Value>,
|
|
||||||
|
v_name: Option<Value>,
|
||||||
|
v_cache: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for File {
|
impl Deref for File {
|
||||||
|
|
@ -48,7 +50,9 @@ impl File {
|
||||||
v_cha: None,
|
v_cha: None,
|
||||||
v_url: None,
|
v_url: None,
|
||||||
v_link_to: None,
|
v_link_to: None,
|
||||||
|
|
||||||
v_name: None,
|
v_name: None,
|
||||||
|
v_cache: None,
|
||||||
})?;
|
})?;
|
||||||
ve.insert(ud.clone());
|
ve.insert(ud.clone());
|
||||||
ud
|
ud
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ pub struct File {
|
||||||
v_cha: Option<Value>,
|
v_cha: Option<Value>,
|
||||||
v_url: Option<Value>,
|
v_url: Option<Value>,
|
||||||
v_link_to: Option<Value>,
|
v_link_to: Option<Value>,
|
||||||
v_name: Option<Value>,
|
|
||||||
|
v_name: Option<Value>,
|
||||||
|
v_cache: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for File {
|
impl Deref for File {
|
||||||
|
|
@ -27,7 +29,7 @@ impl From<File> for yazi_fs::File {
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
pub fn new(inner: yazi_fs::File) -> Self {
|
pub fn new(inner: yazi_fs::File) -> Self {
|
||||||
Self { inner, v_cha: None, v_url: None, v_link_to: None, v_name: None }
|
Self { inner, v_cha: None, v_url: None, v_link_to: None, v_name: None, v_cache: None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,9 @@ macro_rules! impl_file_fields {
|
||||||
$crate::cached_field!($fields, name, |lua, me| {
|
$crate::cached_field!($fields, name, |lua, me| {
|
||||||
me.name().map(|s| lua.create_string(s.as_encoded_bytes())).transpose()
|
me.name().map(|s| lua.create_string(s.as_encoded_bytes())).transpose()
|
||||||
});
|
});
|
||||||
|
$crate::cached_field!($fields, cache, |_, me| Ok(
|
||||||
|
yazi_fs::provider::cache(&me.url).map($crate::Url::new)
|
||||||
|
));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,31 +223,23 @@ footer = { fg = "black", bg = "white" }
|
||||||
|
|
||||||
[filetype]
|
[filetype]
|
||||||
rules = [
|
rules = [
|
||||||
# Images
|
# Image
|
||||||
{ mime = "image/*", fg = "yellow" },
|
{ mime = "image/*", fg = "yellow" },
|
||||||
|
|
||||||
# Media
|
# Media
|
||||||
{ mime = "{audio,video}/*", fg = "magenta" },
|
{ mime = "{audio,video}/*", fg = "magenta" },
|
||||||
|
# Archive
|
||||||
# Archives
|
|
||||||
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "red" },
|
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "red" },
|
||||||
|
# Document
|
||||||
# Documents
|
|
||||||
{ mime = "application/{pdf,doc,rtf}", fg = "cyan" },
|
{ mime = "application/{pdf,doc,rtf}", fg = "cyan" },
|
||||||
|
# Virtual file system
|
||||||
# Empty files
|
{ mime = "vfs/*", fg = "gray" },
|
||||||
# { mime = "inode/empty", fg = "red" },
|
# Special file
|
||||||
|
|
||||||
# Special files
|
|
||||||
{ url = "*", is = "orphan", bg = "red" },
|
{ url = "*", is = "orphan", bg = "red" },
|
||||||
{ url = "*", is = "exec" , fg = "green" },
|
{ url = "*", is = "exec" , fg = "green" },
|
||||||
|
# Dummy file
|
||||||
# Dummy files
|
|
||||||
{ url = "*", is = "dummy", bg = "red" },
|
{ url = "*", is = "dummy", bg = "red" },
|
||||||
{ url = "*/", is = "dummy", bg = "red" },
|
{ url = "*/", is = "dummy", bg = "red" },
|
||||||
|
|
||||||
# Fallback
|
# Fallback
|
||||||
# { url = "*", fg = "white" },
|
|
||||||
{ url = "*/", fg = "blue" }
|
{ url = "*/", fg = "blue" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,31 +223,23 @@ footer = { fg = "black", bg = "white" }
|
||||||
|
|
||||||
[filetype]
|
[filetype]
|
||||||
rules = [
|
rules = [
|
||||||
# Images
|
# Image
|
||||||
{ mime = "image/*", fg = "yellow" },
|
{ mime = "image/*", fg = "yellow" },
|
||||||
|
|
||||||
# Media
|
# Media
|
||||||
{ mime = "{audio,video}/*", fg = "magenta" },
|
{ mime = "{audio,video}/*", fg = "magenta" },
|
||||||
|
# Archive
|
||||||
# Archives
|
|
||||||
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "red" },
|
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "red" },
|
||||||
|
# Document
|
||||||
# Documents
|
|
||||||
{ mime = "application/{pdf,doc,rtf}", fg = "cyan" },
|
{ mime = "application/{pdf,doc,rtf}", fg = "cyan" },
|
||||||
|
# Virtual file system
|
||||||
# Empty files
|
{ mime = "vfs/*", fg = "darkgray" },
|
||||||
# { mime = "inode/empty", fg = "red" },
|
# Special file
|
||||||
|
|
||||||
# Special files
|
|
||||||
{ url = "*", is = "orphan", bg = "red" },
|
{ url = "*", is = "orphan", bg = "red" },
|
||||||
{ url = "*", is = "exec" , fg = "green" },
|
{ url = "*", is = "exec" , fg = "green" },
|
||||||
|
# Dummy file
|
||||||
# Dummy files
|
|
||||||
{ url = "*", is = "dummy", bg = "red" },
|
{ url = "*", is = "dummy", bg = "red" },
|
||||||
{ url = "*/", is = "dummy", bg = "red" },
|
{ url = "*/", is = "dummy", bg = "red" },
|
||||||
|
|
||||||
# Fallback
|
# Fallback
|
||||||
# { url = "*", fg = "white" },
|
|
||||||
{ url = "*/", fg = "blue" }
|
{ url = "*/", fg = "blue" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ spotters = [
|
||||||
{ mime = "image/*", run = "image" },
|
{ mime = "image/*", run = "image" },
|
||||||
# Video
|
# Video
|
||||||
{ mime = "video/*", run = "video" },
|
{ mime = "video/*", run = "video" },
|
||||||
|
# Virtual file system
|
||||||
|
{ mime = "vfs/*", run = "vfs" },
|
||||||
# Fallback
|
# Fallback
|
||||||
{ url = "*", run = "file" },
|
{ url = "*", run = "file" },
|
||||||
]
|
]
|
||||||
|
|
@ -149,6 +151,8 @@ previewers = [
|
||||||
{ mime = "application/ms-opentype", run = "font" },
|
{ mime = "application/ms-opentype", run = "font" },
|
||||||
# Empty file
|
# Empty file
|
||||||
{ mime = "inode/empty", run = "empty" },
|
{ mime = "inode/empty", run = "empty" },
|
||||||
|
# Virtual file system
|
||||||
|
{ mime = "vfs/*", run = "vfs" },
|
||||||
# Fallback
|
# Fallback
|
||||||
{ url = "*", run = "file" },
|
{ url = "*", run = "file" },
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,9 @@ impl Term {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let resp = Emulator::read_until_da1();
|
let resp = Emulator::read_until_da1();
|
||||||
yazi_term::RestoreCursor::store(&resp);
|
Mux::tmux_drain()?;
|
||||||
|
|
||||||
|
yazi_term::RestoreCursor::store(&resp);
|
||||||
CSI_U.store(resp.contains("\x1b[?0u"), Ordering::Relaxed);
|
CSI_U.store(resp.contains("\x1b[?0u"), Ordering::Relaxed);
|
||||||
if CSI_U.load(Ordering::Relaxed) {
|
if CSI_U.load(Ordering::Relaxed) {
|
||||||
PushKeyboardEnhancementFlags(
|
PushKeyboardEnhancementFlags(
|
||||||
|
|
@ -61,7 +62,6 @@ impl Term {
|
||||||
term.hide_cursor()?;
|
term.hide_cursor()?;
|
||||||
term.clear()?;
|
term.clear()?;
|
||||||
term.flush()?;
|
term.flush()?;
|
||||||
Mux::tmux_drain()?;
|
|
||||||
Ok(term)
|
Ok(term)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,12 @@ impl FilesOp {
|
||||||
for (o, n) in map {
|
for (o, n) in map {
|
||||||
let Some(o_p) = o.parent() else { continue };
|
let Some(o_p) = o.parent() else { continue };
|
||||||
let Some(n_p) = n.url.parent() else { continue };
|
let Some(n_p) = n.url.parent() else { continue };
|
||||||
if o_p != n_p {
|
if o_p == n_p {
|
||||||
|
parents.entry_ref(&o_p).or_default().1.insert(o.urn().to_owned(), n);
|
||||||
|
} else {
|
||||||
parents.entry_ref(&o_p).or_default().0.insert(o.urn().to_owned());
|
parents.entry_ref(&o_p).or_default().0.insert(o.urn().to_owned());
|
||||||
|
parents.entry_ref(&n_p).or_default().1.insert(n.urn().to_owned(), n);
|
||||||
}
|
}
|
||||||
parents.entry_ref(&n_p).or_default().1.insert(n.urn().to_owned(), n);
|
|
||||||
}
|
}
|
||||||
for (p, (o, n)) in parents {
|
for (p, (o, n)) in parents {
|
||||||
match (o.is_empty(), n.is_empty()) {
|
match (o.is_empty(), n.is_empty()) {
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ local TYPE_PATS = { "text", "image", "video", "application", "audio", "font", "i
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local function match_mimetype(s)
|
local function match_mimetype(line)
|
||||||
for _, pat in ipairs(TYPE_PATS) do
|
for _, pat in ipairs(TYPE_PATS) do
|
||||||
local typ, sub = s:match(string.format("(%s/)([+-.a-z0-9]+)%%s+$", pat))
|
local typ, sub = line:match(string.format("(%s/)([+-.a-z0-9]+)%%s+$", pat))
|
||||||
if not sub then
|
if not sub then
|
||||||
elseif s:find(typ .. sub, 1, true) == 1 then
|
elseif line:find(typ .. sub, 1, true) == 1 then
|
||||||
return typ:gsub("^x%-", "", 1) .. sub:gsub("^x%-", "", 1):gsub("^vnd%.", "", 1)
|
return typ:gsub("^x%-", "", 1) .. sub:gsub("^x%-", "", 1):gsub("^vnd%.", "", 1)
|
||||||
else
|
else
|
||||||
return nil, true
|
return nil, true
|
||||||
|
|
@ -15,14 +15,27 @@ local function match_mimetype(s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function miss_cache(cache, line)
|
||||||
|
if line:match("^cannot open `.+' %(No such file or directory%)%s+$") then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
local _, err = fs.cha(Url(cache))
|
||||||
|
return err and err.code == 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function M:fetch(job)
|
function M:fetch(job)
|
||||||
local urls = {}
|
local paths, origins = {}, {}
|
||||||
for _, file in ipairs(job.files) do
|
for i, file in ipairs(job.files) do
|
||||||
urls[#urls + 1] = tostring(file.url)
|
if file.cache then
|
||||||
|
paths[i], origins[i] = tostring(file.cache), tostring(file.url)
|
||||||
|
else
|
||||||
|
paths[i] = tostring(file.url)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local cmd = os.getenv("YAZI_FILE_ONE") or "file"
|
local cmd = os.getenv("YAZI_FILE_ONE") or "file"
|
||||||
local child, err = Command(cmd):arg({ "-bL", "--mime-type", "--" }):arg(urls):stdout(Command.PIPED):spawn()
|
local child, err = Command(cmd):arg({ "-bL", "--mime-type", "--" }):arg(paths):stdout(Command.PIPED):spawn()
|
||||||
if not child then
|
if not child then
|
||||||
return true, Err("Failed to start `%s`, error: %s", cmd, err)
|
return true, Err("Failed to start `%s`, error: %s", cmd, err)
|
||||||
end
|
end
|
||||||
|
|
@ -50,13 +63,18 @@ function M:fetch(job)
|
||||||
|
|
||||||
match, ignore = match_mimetype(line)
|
match, ignore = match_mimetype(line)
|
||||||
if match then
|
if match then
|
||||||
updates[urls[i]], state[i], i = match, true, i + 1
|
updates[origins[i] or paths[i]], state[i], i = match, true, i + 1
|
||||||
flush(false)
|
flush(false)
|
||||||
elseif not ignore then
|
elseif ignore then
|
||||||
|
goto continue
|
||||||
|
elseif origins[i] and miss_cache(paths[i], line) then
|
||||||
|
updates[origins[i]], state[i], i = "vfs/todo", true, i + 1
|
||||||
|
flush(false)
|
||||||
|
else
|
||||||
state[i], i = false, i + 1
|
state[i], i = false, i + 1
|
||||||
end
|
end
|
||||||
::continue::
|
::continue::
|
||||||
until i > #urls
|
until i > #paths
|
||||||
|
|
||||||
flush(true)
|
flush(true)
|
||||||
return state
|
return state
|
||||||
|
|
|
||||||
12
yazi-plugin/preset/plugins/vfs.lua
Normal file
12
yazi-plugin/preset/plugins/vfs.lua
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M:peek(job)
|
||||||
|
local line = ui.Line("Remote file, download to preview"):reverse()
|
||||||
|
ya.preview_widget(job, ui.Text(line):area(job.area):wrap(ui.Wrap.YES))
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:seek() end
|
||||||
|
|
||||||
|
function M:spot(job) require("file"):spot(job) end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
@ -43,6 +43,7 @@ impl Default for Loader {
|
||||||
("pdf".to_owned(), preset!("plugins/pdf").into()),
|
("pdf".to_owned(), preset!("plugins/pdf").into()),
|
||||||
("session".to_owned(), preset!("plugins/session").into()),
|
("session".to_owned(), preset!("plugins/session").into()),
|
||||||
("svg".to_owned(), preset!("plugins/svg").into()),
|
("svg".to_owned(), preset!("plugins/svg").into()),
|
||||||
|
("vfs".to_owned(), preset!("plugins/vfs").into()),
|
||||||
("video".to_owned(), preset!("plugins/video").into()),
|
("video".to_owned(), preset!("plugins/video").into()),
|
||||||
("zoxide".to_owned(), preset!("plugins/zoxide").into()),
|
("zoxide".to_owned(), preset!("plugins/zoxide").into()),
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue