refactoring
This commit is contained in:
parent
74ae1150a3
commit
69400c1aa3
142 changed files with 11 additions and 70 deletions
19
modules/home/yazi/plugins/file-extra-metadata.yazi/LICENSE
Normal file
19
modules/home/yazi/plugins/file-extra-metadata.yazi/LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2024 boydaihungst
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
102
modules/home/yazi/plugins/file-extra-metadata.yazi/README.md
Normal file
102
modules/home/yazi/plugins/file-extra-metadata.yazi/README.md
Normal file
|
@ -0,0 +1,102 @@
|
|||
# file-extra-metadata
|
||||
|
||||
<!--toc:start-->
|
||||
|
||||
- [file-extra-metadata](#file-extra-metadata)
|
||||
- [Preview](#preview)
|
||||
- [Before:](#before)
|
||||
- [After:](#after)
|
||||
- [Requirements](#requirements)
|
||||
- [Installation](#installation)
|
||||
- [For developer](#for-developer)
|
||||
<!--toc:end-->
|
||||
|
||||
This is a Yazi plugin that replaces the default file previewer and spotter with extra information.
|
||||
|
||||
## Preview
|
||||
|
||||
### Before:
|
||||
|
||||
- Previewer
|
||||
|
||||

|
||||
|
||||
- Spotter (yazi >= v0.4 after 21/11/2024)
|
||||
|
||||

|
||||
|
||||
### After:
|
||||
|
||||
- Previewer
|
||||
|
||||

|
||||
|
||||
- Spotter (yazi >= v0.4 after 21/11/2024)
|
||||
|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
- [yazi >=0.4](https://github.com/sxyazi/yazi)
|
||||
- Tested on Linux. For MacOS, Windows: some fields will shows empty values.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the plugin:
|
||||
|
||||
```sh
|
||||
ya pack -a boydaihungst/file-extra-metadata
|
||||
```
|
||||
|
||||
Add spotter keybind, makes sure not conflict with other `<Tab>` keybind in
|
||||
`manager` section:
|
||||
|
||||
```toml
|
||||
[manager]
|
||||
keymap = [
|
||||
# ...
|
||||
# Spotting
|
||||
{ on = "<Tab>", run = "spot", desc = "Spot hovered file" },
|
||||
]
|
||||
```
|
||||
|
||||
Create `~/.config/yazi/yazi.toml` and add:
|
||||
|
||||
```toml
|
||||
[plugin]
|
||||
append_previewers = [
|
||||
{ name = "*", run = "file-extra-metadata" },
|
||||
]
|
||||
# yazi v0.4 after 21/11/2024
|
||||
# Setup keybind for spotter: https://github.com/sxyazi/yazi/pull/1802
|
||||
append_spotters = [
|
||||
{ name = "*", run = "file-extra-metadata" },
|
||||
]
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```toml
|
||||
[plugin]
|
||||
previewers = [
|
||||
# ... the rest
|
||||
# disable default file plugin { name = "*", run = "file" },
|
||||
{ name = "*", run = "file-extra-metadata" },
|
||||
]
|
||||
# yazi v0.4 after 21/11/2024
|
||||
# Setup keybind for spotter: https://github.com/sxyazi/yazi/pull/1802
|
||||
spotters = [
|
||||
# ... the rest
|
||||
# Fallback
|
||||
# { name = "*", run = "file" },
|
||||
{ name = "*", run = "file-extra-metadata" },
|
||||
]
|
||||
```
|
||||
|
||||
## For developer
|
||||
|
||||
If you want to compile this with other spotter/previewer:
|
||||
|
||||
```lua
|
||||
require("file-extra-metadata"):render_table(job, { show_plugins_section = true })
|
||||
```
|
489
modules/home/yazi/plugins/file-extra-metadata.yazi/main.lua
Normal file
489
modules/home/yazi/plugins/file-extra-metadata.yazi/main.lua
Normal file
|
@ -0,0 +1,489 @@
|
|||
local M = {}
|
||||
|
||||
local function permission(file)
|
||||
local h = file
|
||||
if not h then
|
||||
return ""
|
||||
end
|
||||
|
||||
local perm = h.cha:perm()
|
||||
if not perm then
|
||||
return ""
|
||||
end
|
||||
|
||||
local spans = ""
|
||||
for i = 1, #perm do
|
||||
local c = perm:sub(i, i)
|
||||
spans = spans .. c
|
||||
end
|
||||
return spans
|
||||
end
|
||||
|
||||
local function link_count(file)
|
||||
local h = file
|
||||
if h == nil or ya.target_family() ~= "unix" then
|
||||
return ""
|
||||
end
|
||||
|
||||
return h.cha.nlink
|
||||
end
|
||||
|
||||
local function owner_group(file)
|
||||
local h = file
|
||||
if h == nil or ya.target_family() ~= "unix" then
|
||||
return ""
|
||||
end
|
||||
return (ya.user_name(h.cha.uid) or tostring(h.cha.uid)) .. "/" .. (ya.group_name(h.cha.gid) or tostring(h.cha.gid))
|
||||
end
|
||||
|
||||
local file_size_and_folder_childs = function(file)
|
||||
local h = file
|
||||
if not h or h.cha.is_link then
|
||||
return ""
|
||||
end
|
||||
|
||||
return h.cha.len and ya.readable_size(h.cha.len) or ""
|
||||
end
|
||||
|
||||
--- get file timestamp
|
||||
---@param file any
|
||||
---@param type "mtime" | "atime" | "btime"
|
||||
---@return any
|
||||
local function fileTimestamp(file, type)
|
||||
local h = file
|
||||
if not h or h.cha.is_link then
|
||||
return ""
|
||||
end
|
||||
local time = math.floor(h.cha[type] or 0)
|
||||
if time == 0 then
|
||||
return ""
|
||||
else
|
||||
return os.date("%Y-%m-%d %H:%M", time)
|
||||
end
|
||||
end
|
||||
|
||||
-- Function to split a string by spaces (considering multiple spaces as one delimiter)
|
||||
local function split_by_whitespace(input)
|
||||
local result = {}
|
||||
for word in string.gmatch(input, "%S+") do
|
||||
table.insert(result, word)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function get_filesystem_extra(file)
|
||||
local result = {
|
||||
filesystem = "",
|
||||
device = "",
|
||||
type = "",
|
||||
used_space = "",
|
||||
avail_space = "",
|
||||
total_space = "",
|
||||
used_space_percent = "",
|
||||
avail_space_percent = "",
|
||||
error = nil,
|
||||
}
|
||||
local h = file
|
||||
local file_url = tostring(h.url)
|
||||
if not h or ya.target_family() ~= "unix" then
|
||||
return result
|
||||
end
|
||||
|
||||
local output, _ = Command("tail")
|
||||
:args({ "-n", "-1" })
|
||||
:stdin(Command("df"):args({ "-P", "-T", "-h", file_url }):stdout(Command.PIPED):spawn():take_stdout())
|
||||
:stdout(Command.PIPED)
|
||||
:output()
|
||||
|
||||
if output then
|
||||
-- Splitting the data
|
||||
local parts = split_by_whitespace(output.stdout)
|
||||
|
||||
-- Display the result
|
||||
for i, part in ipairs(parts) do
|
||||
if i == 1 then
|
||||
result.filesystem = part
|
||||
elseif i == 2 then
|
||||
result.device = part
|
||||
elseif i == 3 then
|
||||
result.total_space = part
|
||||
elseif i == 4 then
|
||||
result.used_space = part
|
||||
elseif i == 5 then
|
||||
result.avail_space = part
|
||||
elseif i == 6 then
|
||||
result.used_space_percent = part
|
||||
result.avail_space_percent = 100 - tonumber((string.match(part, "%d+") or "0"))
|
||||
elseif i == 7 then
|
||||
result.type = part
|
||||
end
|
||||
end
|
||||
else
|
||||
result.error = "tail, df are installed?"
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function attributes(file)
|
||||
local h = file
|
||||
local file_url = tostring(h.url)
|
||||
if not h or ya.target_family() ~= "unix" then
|
||||
return ""
|
||||
end
|
||||
|
||||
local output, _ = Command("lsattr"):args({ "-d", file_url }):stdout(Command.PIPED):output()
|
||||
|
||||
if output then
|
||||
-- Splitting the data
|
||||
local parts = split_by_whitespace(output.stdout)
|
||||
|
||||
-- Display the result
|
||||
for i, part in ipairs(parts) do
|
||||
if i == 1 then
|
||||
return part
|
||||
end
|
||||
end
|
||||
return ""
|
||||
else
|
||||
return "lsattr is installed?"
|
||||
end
|
||||
end
|
||||
|
||||
---shorten string
|
||||
---@param _s string string
|
||||
---@param _t string tail
|
||||
---@param _w number max characters
|
||||
---@return string
|
||||
local shorten = function(_s, _t, _w)
|
||||
local s = _s or utf8.len(_s)
|
||||
local t = _t or ""
|
||||
local ellipsis = "…" .. t
|
||||
local w = _w < utf8.len(ellipsis) and utf8.len(ellipsis) or _w
|
||||
local n_ellipsis = utf8.len(ellipsis) or 0
|
||||
if utf8.len(s) > w then
|
||||
return s:sub(1, (utf8.offset(s, w - n_ellipsis + 1) or 2) - 1) .. ellipsis
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local is_supported_table = type(ui.Table) ~= "nil" and type(ui.Row) ~= "nil"
|
||||
|
||||
local styles = {
|
||||
header = ui.Style():fg("green"),
|
||||
row_label = ui.Style():fg("reset"),
|
||||
row_value = ui.Style():fg("blue"),
|
||||
row_value_spot_hovered = ui.Style():fg("blue"):reverse(),
|
||||
}
|
||||
|
||||
function M:render_table(job, opts)
|
||||
local filesystem_extra = get_filesystem_extra(job.file)
|
||||
local prefix = " "
|
||||
local label_lines, value_lines, rows = {}, {}, {}
|
||||
local label_max_length = 15
|
||||
local file_name_extension = job.file.cha.is_dir and "…" or ("." .. (job.file.url.ext(job.file.url) or ""))
|
||||
|
||||
local row = function(key, value)
|
||||
local h = type(value) == "table" and #value or 1
|
||||
rows[#rows + 1] = ui.Row({ ui.Line(key):style(styles.row_label), ui.Line(value):style(styles.row_value) })
|
||||
:height(h)
|
||||
end
|
||||
|
||||
local file_name = shorten(
|
||||
job.file.name,
|
||||
file_name_extension,
|
||||
math.floor(job.area.w - label_max_length - utf8.len(file_name_extension))
|
||||
)
|
||||
local location =
|
||||
shorten(tostring(job.file.url:parent()), "", math.floor(job.area.w - label_max_length - utf8.len(prefix)))
|
||||
local filesystem_error = filesystem_extra.error
|
||||
and shorten(filesystem_extra.error, "", math.floor(job.area.w - label_max_length - utf8.len(prefix)))
|
||||
or nil
|
||||
local filesystem =
|
||||
shorten(filesystem_extra.filesystem, "", math.floor(job.area.w - label_max_length - utf8.len(prefix)))
|
||||
|
||||
if not is_supported_table then
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span("Metadata:"),
|
||||
}):style(styles.header)
|
||||
)
|
||||
table.insert(
|
||||
value_lines,
|
||||
ui.Line({
|
||||
ui.Span(""),
|
||||
})
|
||||
)
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("File:"),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(
|
||||
value_lines,
|
||||
ui.Line({
|
||||
ui.Span(file_name),
|
||||
}):style(styles.row_value)
|
||||
)
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Mimetype: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(ui.Span(job._mime or job.mime)):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Location: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(
|
||||
value_lines,
|
||||
ui.Line({
|
||||
ui.Span(location),
|
||||
}):style(styles.row_value)
|
||||
)
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Mode: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(permission(job.file)):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Attributes: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(ui.Span(attributes(job.file))):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Links: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(
|
||||
value_lines,
|
||||
ui.Line({
|
||||
ui.Span(tostring(link_count(job.file))),
|
||||
}):style(styles.row_value)
|
||||
)
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Owner: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(ui.Span(owner_group(job.file))):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Size: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(ui.Span(file_size_and_folder_childs(job.file))):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Created: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(ui.Span(fileTimestamp(job.file, "btime"))):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Modified: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(ui.Span(fileTimestamp(job.file, "mtime"))):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Accessed: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(ui.Span(fileTimestamp(job.file, "atime"))):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Filesystem: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(ui.Span(filesystem_error or filesystem)):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Device: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(ui.Span(filesystem_error or filesystem_extra.device)):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Type: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(value_lines, ui.Line(ui.Span(filesystem_error or filesystem_extra.type)):style(styles.row_value))
|
||||
|
||||
table.insert(
|
||||
label_lines,
|
||||
ui.Line({
|
||||
ui.Span(prefix),
|
||||
ui.Span("Free space: "),
|
||||
}):style(styles.row_label)
|
||||
)
|
||||
table.insert(
|
||||
value_lines,
|
||||
ui.Line(
|
||||
ui.Span(
|
||||
filesystem_extra.error
|
||||
or (
|
||||
filesystem_extra.avail_space
|
||||
.. " / "
|
||||
.. filesystem_extra.total_space
|
||||
.. " ("
|
||||
.. filesystem_extra.avail_space_percent
|
||||
.. "%)"
|
||||
)
|
||||
)
|
||||
):style(styles.row_value)
|
||||
)
|
||||
else
|
||||
rows[#rows + 1] = ui.Row({ "Metadata", "" }):style(styles.header)
|
||||
row(prefix .. "File:", file_name)
|
||||
row(prefix .. "Mimetype:", job.mime)
|
||||
row(prefix .. "Location:", location)
|
||||
row(prefix .. "Mode:", permission(job.file))
|
||||
row(prefix .. "Attributes:", attributes(job.file))
|
||||
row(prefix .. "Links:", tostring(link_count(job.file)))
|
||||
row(prefix .. "Owner:", owner_group(job.file))
|
||||
row(prefix .. "Size:", file_size_and_folder_childs(job.file))
|
||||
row(prefix .. "Created:", fileTimestamp(job.file, "btime"))
|
||||
row(prefix .. "Modified:", fileTimestamp(job.file, "mtime"))
|
||||
row(prefix .. "Accessed:", fileTimestamp(job.file, "atime"))
|
||||
row(prefix .. "Filesystem:", filesystem_error or filesystem)
|
||||
row(prefix .. "Device:", filesystem_error or filesystem_extra.device)
|
||||
row(prefix .. "Type:", filesystem_error or filesystem_extra.type)
|
||||
row(
|
||||
prefix .. "Free space:",
|
||||
filesystem_error
|
||||
or (
|
||||
(
|
||||
filesystem_extra.avail_space
|
||||
and filesystem_extra.total_space
|
||||
and filesystem_extra.avail_space_percent
|
||||
)
|
||||
and (filesystem_extra.avail_space .. " / " .. filesystem_extra.total_space .. " (" .. filesystem_extra.avail_space_percent .. "%)")
|
||||
or ""
|
||||
)
|
||||
)
|
||||
if opts and opts.show_plugins_section and PLUGIN then
|
||||
local spotter = PLUGIN.spotter(job.file.url, job.mime)
|
||||
local previewer = PLUGIN.previewer(job.file.url, job.mime)
|
||||
local fetchers = PLUGIN.fetchers(job.file, job.mime)
|
||||
local preloaders = PLUGIN.preloaders(job.file.url, job.mime)
|
||||
|
||||
for i, v in ipairs(fetchers) do
|
||||
fetchers[i] = v.cmd
|
||||
end
|
||||
for i, v in ipairs(preloaders) do
|
||||
preloaders[i] = v.cmd
|
||||
end
|
||||
|
||||
rows[#rows + 1] = ui.Row({ { "", "Plugins" }, "" }):height(2):style(styles.header)
|
||||
row(prefix .. "Spotter:", spotter and spotter.cmd or "")
|
||||
row(prefix .. "Previewer:", previewer and previewer.cmd or "")
|
||||
row(prefix .. "Fetchers:", #fetchers ~= 0 and fetchers or "")
|
||||
row(prefix .. "Preloaders:", #preloaders ~= 0 and preloaders or "")
|
||||
end
|
||||
end
|
||||
|
||||
if not is_supported_table then
|
||||
local areas = ui.Layout()
|
||||
:direction(ui.Layout.HORIZONTAL)
|
||||
:constraints({ ui.Constraint.Length(label_max_length), ui.Constraint.Fill(1) })
|
||||
:split(job.area)
|
||||
local label_area = areas[1]
|
||||
local value_area = areas[2]
|
||||
return {
|
||||
ui.Text(label_lines):area(label_area):align(ui.Text.LEFT):wrap(ui.Text.WRAP_NO),
|
||||
ui.Text(value_lines):area(value_area):align(ui.Text.LEFT):wrap(ui.Text.WRAP_NO),
|
||||
}
|
||||
else
|
||||
return {
|
||||
ui.Table(rows):area(job.area):row(1):col(1):col_style(styles.row_value):widths({
|
||||
ui.Constraint.Length(label_max_length),
|
||||
ui.Constraint.Fill(1),
|
||||
}),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function M:peek(job)
|
||||
local start, cache = os.clock(), ya.file_cache(job)
|
||||
if not cache or self:preload(job) ~= 1 then
|
||||
return 1
|
||||
end
|
||||
ya.sleep(math.max(0, PREVIEW.image_delay / 1000 + start - os.clock()))
|
||||
ya.preview_widgets(job, self:render_table(job))
|
||||
end
|
||||
|
||||
function M:seek(job)
|
||||
local h = cx.active.current.hovered
|
||||
if h and h.url == job.file.url then
|
||||
local step = math.floor(job.units * job.area.h / 10)
|
||||
ya.manager_emit("peek", {
|
||||
tostring(math.max(0, cx.active.preview.skip + step)),
|
||||
only_if = tostring(job.file.url),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cache = ya.file_cache(job)
|
||||
if not cache or fs.cha(cache) then
|
||||
return 1
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
function M:spot(job)
|
||||
job.area = ui.Pos({ "center", w = 80, h = 25 })
|
||||
ya.spot_table(
|
||||
job,
|
||||
self:render_table(job, { show_plugins_section = true })[1]:cell_style(styles.row_value_spot_hovered)
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
Loading…
Add table
Add a link
Reference in a new issue