Update 2024-01-23

This commit is contained in:
Kristofers Solo 2025-01-23 19:13:34 +02:00
parent ff0f2da5d2
commit 3e77832510
73 changed files with 122 additions and 6272 deletions

View File

@ -122,7 +122,7 @@ depends = [ "misc", "local", "eww", "lock" ]
"config/spotify-tui.yml" = "~/.config/spotify-tui/config.yml" "config/spotify-tui.yml" = "~/.config/spotify-tui/config.yml"
[terminal] [terminal]
depends = [ "zsh", "tmux", "yazi" ] depends = [ "zsh", "tmux" ]
[terminal.files] [terminal.files]
"config/alacritty/" = "~/.config/alacritty/" "config/alacritty/" = "~/.config/alacritty/"

0
config/HybridBar/scripts/change-active-workspace Normal file → Executable file
View File

0
config/HybridBar/scripts/get-active-workspace Normal file → Executable file
View File

0
config/HybridBar/scripts/get-window-title Normal file → Executable file
View File

0
config/HybridBar/scripts/get-workspaces Normal file → Executable file
View File

0
config/eww/scripts/change-active-workspace Normal file → Executable file
View File

0
config/eww/scripts/get-active-workspace Normal file → Executable file
View File

0
config/eww/scripts/get-music Normal file → Executable file
View File

0
config/eww/scripts/get-network Normal file → Executable file
View File

0
config/eww/scripts/get-window-title Normal file → Executable file
View File

0
config/eww/scripts/get-workspaces Normal file → Executable file
View File

0
config/eww/scripts/getvol Normal file → Executable file
View File

0
config/eww/scripts/github Normal file → Executable file
View File

0
config/lf/cleaner Normal file → Executable file
View File

0
config/lf/lfrc Normal file → Executable file
View File

0
config/nsxiv/exec/image-info Normal file → Executable file
View File

0
config/nsxiv/exec/key-handler Normal file → Executable file
View File

0
config/nsxiv/exec/nsxiv-url Normal file → Executable file
View File

0
config/nsxiv/exec/thumb-info Normal file → Executable file
View File

0
config/nsxiv/exec/win-title Normal file → Executable file
View File

View File

@ -114,4 +114,3 @@ export _JAVA_AWT_WM_NONREPARENTING=1 # Fix for Java applications in dwm
. "$XDG_DATA_HOME/cargo/env" . "$XDG_DATA_HOME/cargo/env"
. "$XDG_DATA_HOME/rye/env" . "$XDG_DATA_HOME/rye/env"
. "$XDG_CACHE_HOME/deno/.deno/env" . "$XDG_CACHE_HOME/deno/.deno/env"

0
config/x11/opt-apps Normal file → Executable file
View File

View File

@ -1,22 +1,82 @@
[plugin] [[plugin.deps]]
deps = [ use = "AnirudhG07/nbpreview"
{use = "AnirudhG07/nbpreview", rev = "1d85745" }, rev = "1d85745"
{use = "Reledia/glow", rev = "5ce76dc" }, hash = "d378328e5d0a1b9fb9f04ab3aade4575"
{use = "Reledia/hexyl", rev = "39d3d4e" },
{use = "Reledia/miller", rev = "40e0265" }, [[plugin.deps]]
{use = "Sonico98/exifaudio", rev = "d794614" }, use = "Reledia/glow"
{use = "dedukun/relative-motions", rev = "df97039" }, rev = "5ce76dc"
{use = "hankertrix/augment-command", rev = "6a367db" }, hash = "52e5f5c602962e7cbf874da28f52ba45"
{use = "imsi32/yatline", rev = "600ed1f" },
{use = "kirasok/torrent-preview", rev = "c9e67df" }, [[plugin.deps]]
{use = "ndtoan96/ouch", rev = "b869886" }, use = "Reledia/hexyl"
{use = "pirafrank/what-size", rev = "b23e3a4" }, rev = "39d3d4e"
{use = "yazi-rs/plugins:chmod", rev = "71c4fc2" }, hash = "dd624cbaff94af65f39fd86bc57b340"
{use = "yazi-rs/plugins:full-border", rev = "71c4fc2" },
{use = "yazi-rs/plugins:git", rev = "71c4fc2" }, [[plugin.deps]]
{use = "yazi-rs/plugins:hide-preview", rev = "71c4fc2" }, use = "Reledia/miller"
{use = "yazi-rs/plugins:max-preview", rev = "71c4fc2" }, rev = "40e0265"
] hash = "2d6d77583162aaf0a599e7a3091b5878"
[[plugin.deps]]
use = "Sonico98/exifaudio"
rev = "d794614"
hash = "a8e15d3c21c02a5af41d46ed04778a02"
[[plugin.deps]]
use = "dedukun/relative-motions"
rev = "df97039"
hash = "395940d2b22941e0acb1232579c9d4cf"
[[plugin.deps]]
use = "hankertrix/augment-command"
rev = "c0fd61f"
hash = "3e56e8b9ee07aabc0d08743c05835929"
[[plugin.deps]]
use = "imsi32/yatline"
rev = "9328205"
hash = "3e51d1fd8a2e481fcfa8eab1251d1c5f"
[[plugin.deps]]
use = "kirasok/torrent-preview"
rev = "c9e67df"
hash = "f0d9a684da8e4ab9ccbcd255a97cf42b"
[[plugin.deps]]
use = "ndtoan96/ouch"
rev = "083d564"
hash = "1e4c0ac1fca31a23412324710193358a"
[[plugin.deps]]
use = "pirafrank/what-size"
rev = "b23e3a4"
hash = "98e5f5af3efd3ba8bc2db0720187cc83"
[[plugin.deps]]
use = "yazi-rs/plugins:chmod"
rev = "6418698"
hash = "4c7e8fd0266eedee7b619d966bd2d025"
[[plugin.deps]]
use = "yazi-rs/plugins:full-border"
rev = "6418698"
hash = "882ed23839778f82dc137248979c8681"
[[plugin.deps]]
use = "yazi-rs/plugins:git"
rev = "6418698"
hash = "e9cf1bfc03de7fee0f1d4260da0d1dfd"
[[plugin.deps]]
use = "yazi-rs/plugins:hide-preview"
rev = "6418698"
hash = "5be5885898ca9df783bdec0d402bf4b0"
[[plugin.deps]]
use = "yazi-rs/plugins:max-preview"
rev = "6418698"
hash = "9bc26d10d2f6e2aa93b10905b1b76979"
[flavor] [flavor]
deps = [ ] deps = []

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Ciarán O'Brien
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.

View File

@ -1,48 +0,0 @@
# ~~archive.yazi~~ compress.yazi
A Yazi plugin that compresses selected files to an archive. Supporting yazi versions 0.2.5 and up.
## Supported file types
| Extention | Unix Command | Windows Command |
| ------------- | ------------- | --------------- |
| .zip | zip -r | 7z a -tzip |
| .7z | 7z a | 7z a |
| .tar | tar rpf | tar rpf |
| .tar.gz | gzip | 7z a -tgzip |
| .tar.xz | xz | 7z a -txz |
| .tar.bz2 | bzip2 | 7z a -tbzip2 |
| .tar.zst | zstd | zstd |
**NOTE:** Windows users are required to install 7-Zip and add 7z.exe to the `path` environment variable, only tar archives will be available otherwise.
## Install
```bash
# For Unix platforms
git clone https://github.com/KKV9/compress.yazi.git ~/.config/yazi/plugins/compress.yazi
## For Windows
git clone https://github.com/KKV9/compress.yazi.git %AppData%\yazi\config\plugins\compress.yazi
# Or with yazi plugin manager
ya pack -a KKV9/compress
```
- Add this to your `keymap.toml`:
```toml
[[manager.prepend_keymap]]
on = [ "c", "a" ]
run = "plugin compress"
desc = "Archive selected files"
```
## Usage
- Select files or folders to add, then press `c` `a` to create a new archive.
- Type a name for the new file.
- The file extention must match one of the supported filetype extentions.
- The desired archive/compression command must be installed on your system.

View File

@ -1,228 +0,0 @@
-- Send error notification
local function notify_error(message, urgency)
ya.notify({
title = "Archive",
content = message,
level = urgency,
timeout = 5,
})
end
-- Check for windows
local is_windows = ya.target_family() == "windows"
-- Make table of selected or hovered: path = filenames
local selected_or_hovered = ya.sync(function()
local tab, paths, names, path_fnames = cx.active, {}, {}, {}
for _, u in pairs(tab.selected) do
paths[#paths + 1] = tostring(u:parent())
names[#names + 1] = tostring(u:name())
end
if #paths == 0 and tab.current.hovered then
paths[1] = tostring(tab.current.hovered.url:parent())
names[1] = tostring(tab.current.hovered.name)
end
for idx, name in ipairs(names) do
if not path_fnames[paths[idx]] then
path_fnames[paths[idx]] = {}
end
table.insert(path_fnames[paths[idx]], name)
end
return path_fnames, tostring(tab.current.cwd)
end)
-- Check if archive command is available
local function is_command_available(cmd)
local stat_cmd
if is_windows then
stat_cmd = string.format("where %s > nul 2>&1", cmd)
else
stat_cmd = string.format("command -v %s >/dev/null 2>&1", cmd)
end
local cmd_exists = os.execute(stat_cmd)
if cmd_exists then
return true
else
return false
end
end
-- Archive command list --> string
local function find_binary(cmd_list)
for _, cmd in ipairs(cmd_list) do
if is_command_available(cmd) then
return cmd
end
end
return cmd_list[1] -- Return first command as fallback
end
-- Check if file exists
local function file_exists(name)
local f = io.open(name, "r")
if f ~= nil then
io.close(f)
return true
else
return false
end
end
-- Append filename to it's parent directory
local function combine_url(path, file)
path, file = Url(path), Url(file)
return tostring(path:join(file))
end
return {
entry = function()
-- Exit visual mode
ya.manager_emit("escape", { visual = true })
-- Define file table and output_dir (pwd)
local path_fnames, output_dir = selected_or_hovered()
-- Get input
local output_name, event = ya.input({
title = "Create archive:",
position = { "top-center", y = 3, w = 40 },
})
if event ~= 1 then
return
end
-- Use appropriate archive command
local archive_commands = {
["%.zip$"] = { command = "zip", args = { "-r" } },
["%.7z$"] = { command = { "7z", "7zz" }, args = { "a" } },
["%.tar.gz$"] = { command = "tar", args = { "rpf" }, compress = "gzip" },
["%.tar.xz$"] = { command = "tar", args = { "rpf" }, compress = "xz" },
["%.tar.bz2$"] = { command = "tar", args = { "rpf" }, compress = "bzip2" },
["%.tar.zst$"] = { command = "tar", args = { "rpf" }, compress = "zstd", compress_args = { "--rm" } },
["%.tar$"] = { command = "tar", args = { "rpf" } },
}
if is_windows then
archive_commands = {
["%.zip$"] = { command = "7z", args = { "a", "-tzip" } },
["%.7z$"] = { command = "7z", args = { "a" } },
["%.tar.gz$"] = {
command = "tar",
args = { "rpf" },
compress = "7z",
compress_args = { "a", "-tgzip", "-sdel", output_name },
},
["%.tar.xz$"] = {
command = "tar",
args = { "rpf" },
compress = "7z",
compress_args = { "a", "-txz", "-sdel", output_name },
},
["%.tar.bz2$"] = {
command = "tar",
args = { "rpf" },
compress = "7z",
compress_args = { "a", "-tbzip2", "-sdel", output_name },
},
["%.tar.zst$"] = { command = "tar", args = { "rpf" }, compress = "zstd", compress_args = { "--rm" } },
["%.tar$"] = { command = "tar", args = { "rpf" } },
}
end
-- Match user input to archive command
local archive_cmd, archive_args, archive_compress, archive_compress_args
for pattern, cmd_pair in pairs(archive_commands) do
if output_name:match(pattern) then
archive_cmd = cmd_pair.command
archive_args = cmd_pair.args
archive_compress = cmd_pair.compress
archive_compress_args = cmd_pair.compress_args or {}
end
end
-- Check if archive command has multiple names
if type(archive_cmd) == "table" then
archive_cmd = find_binary(archive_cmd)
end
-- Check if no archive command is available for the extention
if not archive_cmd then
notify_error("Unsupported file extention", "error")
return
end
-- Exit if archive command is not available
if not is_command_available(archive_cmd) then
notify_error(string.format("%s not available", archive_cmd), "error")
return
end
-- Exit if compress command is not available
if archive_compress and not is_command_available(archive_compress) then
notify_error(string.format("%s compression not available", archive_compress), "error")
return
end
-- If file exists show overwrite prompt
local output_url = combine_url(output_dir, output_name)
while true do
if file_exists(output_url) then
local overwrite_answer = ya.input({
title = "Overwrite " .. output_name .. "? y/N:",
position = { "top-center", y = 3, w = 40 },
})
if overwrite_answer:lower() ~= "y" then
notify_error("Operation canceled", "warn")
return -- If no overwrite selected, exit
else
local rm_status, rm_err = os.remove(output_url)
if not rm_status then
notify_error(string.format("Failed to remove %s, exit code %s", output_name, rm_err), "error")
return
end -- If overwrite fails, exit
end
end
if archive_compress and not output_name:match("%.tar$") then
output_name = output_name:match("(.*%.tar)") -- Test for .tar and .tar.*
output_url = combine_url(output_dir, output_name) -- Update output_url
else
break
end
end
-- Add to output archive in each path, their respective files
for path, names in pairs(path_fnames) do
local archive_status, archive_err =
Command(archive_cmd):args(archive_args):arg(output_url):args(names):cwd(path):spawn():wait()
if not archive_status or not archive_status.success then
notify_error(
string.format(
"%s with selected files failed, exit code %s",
archive_args,
archive_status and archive_status.code or archive_err
),
"error"
)
end
end
-- Use compress command if needed
if archive_compress then
local compress_status, compress_err =
Command(archive_compress):args(archive_compress_args):arg(output_name):cwd(output_dir):spawn():wait()
if not compress_status or not compress_status.success then
notify_error(
string.format(
"%s with %s failed, exit code %s",
archive_compress,
output_name,
compress_status and compress_status.code or compress_err
),
"error"
)
end
end
end,
}

File diff suppressed because it is too large Load Diff

View File

@ -76,6 +76,7 @@
---@field archive_path string|nil The path to the archive ---@field archive_path string|nil The path to the archive
---@field destination_path string|nil The path to the destination ---@field destination_path string|nil The path to the destination
---@field extracted_items_path string|nil The path to the extracted items ---@field extracted_items_path string|nil The path to the extracted items
---@field extractor_name string|nil The name of the extractor
-- The name of the plugin -- The name of the plugin
---@type string ---@type string
@ -2137,16 +2138,6 @@ local function recursively_extract_archive(
local get_extractor_result, extractor = local get_extractor_result, extractor =
get_extractor(archive_path, tostring(temporary_directory_url), config) get_extractor(archive_path, tostring(temporary_directory_url), config)
-- Function to add the archive and destination path to the result
---@param result ExtractionResult The result to add the paths to
---@return ExtractionResult modified_result The result with the paths added
local function add_paths_to_result(result)
return merge_tables(result, {
archive_path = archive_path,
destination_path = destination_path,
})
end
-- If there is no extractor, return the result -- If there is no extractor, return the result
if not extractor then if not extractor then
return merge_tables(get_extractor_result, { return merge_tables(get_extractor_result, {
@ -2155,6 +2146,21 @@ local function recursively_extract_archive(
}) })
end end
-- Function to add additional information to the extraction result
-- The additional information are:
-- - The archive path
-- - The destination path
-- - The name of the extractor
---@param result ExtractionResult The result to add the paths to
---@return ExtractionResult modified_result The result with the paths added
local function add_additional_info(result)
return merge_tables(result, {
archive_path = archive_path,
destination_path = destination_path,
extractor_name = extractor.name,
})
end
-- Get the list of archive files and directories, -- Get the list of archive files and directories,
-- the error message and the password -- the error message and the password
local archive_files, archive_directories, error = extractor:get_items() local archive_files, archive_directories, error = extractor:get_items()
@ -2171,7 +2177,7 @@ local function recursively_extract_archive(
} }
-- Return the extraction result -- Return the extraction result
return add_paths_to_result(extraction_result) return add_additional_info(extraction_result)
end end
-- Get if the archive has only one file -- Get if the archive has only one file
@ -2183,7 +2189,7 @@ local function recursively_extract_archive(
-- If the extraction result is not successful, return it -- If the extraction result is not successful, return it
if not extraction_result.successful then if not extraction_result.successful then
return add_paths_to_result(extraction_result) return add_additional_info(extraction_result)
end end
-- Get the result of moving the extracted items -- Get the result of moving the extracted items
@ -2202,7 +2208,7 @@ local function recursively_extract_archive(
or not extracted_items_path or not extracted_items_path
or not config.recursively_extract_archives or not config.recursively_extract_archives
then then
return add_paths_to_result(move_result) return add_additional_info(move_result)
end end
-- Get the url of the extracted items path -- Get the url of the extracted items path
@ -2272,20 +2278,38 @@ local function recursively_extract_archive(
end end
-- Return the move result -- Return the move result
return add_paths_to_result(move_result) return add_additional_info(move_result)
end end
-- Function to show an extraction error -- Function to show an extraction error
---@param extraction_result ExtractionResult The extraction result ---@param extraction_result ExtractionResult The extraction result
---@return nil ---@return nil
local function show_extraction_error(extraction_result) local function show_extraction_error(extraction_result)
--
-- The line for the error
local error_line = string.format("Error: %s", extraction_result.error)
-- If the extractor name exists
if extraction_result.extractor_name then
--
-- Add the extractor's name to the error
error_line = string.format(
"%s error: %s",
extraction_result.extractor_name,
extraction_result.error
)
end
-- Show the extraction error
return show_error(table.concat({ return show_error(table.concat({
string.format( string.format(
"Failed to extract archive at: %s", "Failed to extract archive at: %s",
extraction_result.archive_path extraction_result.archive_path
), ),
string.format("Destination: %s", extraction_result.destination_path), string.format("Destination: %s", extraction_result.destination_path),
string.format("Error: %s", extraction_result.error), error_line,
}, "\n")) }, "\n"))
end end

View File

@ -1,39 +0,0 @@
local selected_or_hovered = ya.sync(function()
local tab, paths = cx.active, {}
for _, u in pairs(tab.selected) do
paths[#paths + 1] = tostring(u)
end
if #paths == 0 and tab.current.hovered then
paths[1] = tostring(tab.current.hovered.url)
end
return paths
end)
return {
entry = function()
ya.manager_emit("escape", { visual = true })
local urls = selected_or_hovered()
if #urls == 0 then
return ya.notify { title = "Chmod", content = "No file selected", level = "warn", timeout = 5 }
end
local value, event = ya.input {
title = "Chmod:",
position = { "top-center", y = 3, w = 40 },
}
if event ~= 1 then
return
end
local status, err = Command("chmod"):arg(value):args(urls):spawn():wait()
if not status or not status.success then
ya.notify {
title = "Chmod",
content = string.format("Chmod on selected files failed, error: %s", status and status.code or err),
level = "error",
timeout = 5,
}
end
end,
}

View File

@ -1,231 +0,0 @@
local M = {}
function GetPath(str)
local sep = '/'
if ya.target_family() == "windows" then
sep = '\\'
end
return str:match("(.*"..sep..")")
end
function Exiftool(...)
local child = Command("exiftool")
:args({
"-q", "-q", "-S", "-Title", "-SortName",
"-TitleSort", "-TitleSortOrder", "-Artist",
"-SortArtist", "-ArtistSort", "-PerformerSortOrder",
"-Album", "-SortAlbum", "-AlbumSort", "-AlbumSortOrder",
"-AlbumArtist", "-SortAlbumArtist", "-AlbumArtistSort",
"-AlbumArtistSortOrder", "-Genre", "-TrackNumber",
"-Year", "-Duration", "-SampleRate",
"-AudioSampleRate", "-AudioBitrate", "-AvgBitrate",
"-Channels", "-AudioChannels", tostring(...),
})
:stdout(Command.PIPED)
:stderr(Command.NULL)
:spawn()
return child
end
function Mediainfo(...)
local file, cache_dir = ...
local template = cache_dir.."mediainfo.txt"
local child = Command("mediainfo")
:args({
"--Output=file://"..template, tostring(file)
})
:stdout(Command.PIPED)
:stderr(Command.NULL)
:spawn()
return child
end
function M:peek(job)
local cache = ya.file_cache(job)
if not cache then
return
end
-- Get cache dir to find the mediainfo template file
local cache_dir = GetPath(tostring(cache))
-- Try mediainfo, otherwise use exiftool
local status, child = pcall(Mediainfo, job.file.url, cache_dir)
if not status or child == nil then
status, child = pcall(Exiftool, job.file.url)
if not status or child == nil then
local error = ui.Line { ui.Span("Make sure exiftool is installed and in your PATH") }
-- TODO)) Remove legacy method when v0.4 gets released
local function display_error_legacy()
local p = ui.Paragraph(job.area, { error }):wrap(ui.Paragraph.WRAP)
ya.preview_widgets(job, { p })
end
local function display_error()
local p = ui.Text(error):area(job.area):wrap(ui.Text.WRAP)
ya.preview_widgets(job, { p })
end
if pcall(display_error) then else pcall(display_error_legacy) end
return
end
end
local limit = job.area.h
local i, metadata = 0, {}
repeat
local next, event = child:read_line()
if event == 1 then
return self:fallback_to_builtin()
elseif event ~= 0 then
break
end
i = i + 1
if i > job.skip then
local m_title, m_tag = Prettify(next)
if m_title ~= "" and m_tag ~= "" then
local ti = ui.Span(m_title):bold()
local ta = ui.Span(m_tag)
table.insert(metadata, ui.Line{ti, ta})
table.insert(metadata, ui.Line{})
end
end
until i >= job.skip + limit
-- TODO)) Remove legacy method when v0.4 gets released
local function display_metadata_legacy()
local p = ui.Paragraph(job.area, metadata):wrap(ui.Paragraph.WRAP)
ya.preview_widgets(job, { p })
end
local function display_metadata()
local p = ui.Text(metadata):area(job.area):wrap(ui.Text.WRAP)
ya.preview_widgets(job, { p })
end
if pcall(display_metadata) then else pcall(display_metadata_legacy) end
local cover_width = job.area.w / 2 - 5
local cover_height = (job.area.h / 4) + 3
local bottom_right = ui.Rect {
x = job.area.right - cover_width,
y = job.area.bottom - cover_height,
w = cover_width,
h = cover_height,
}
if self:preload(job) == 1 then
ya.image_show(cache, bottom_right)
end
end
function Prettify(metadata)
local substitutions = {
Sortname = "Sort Title:",
SortName = "Sort Title:",
TitleSort = "Sort Title:",
TitleSortOrder = "Sort Title:",
ArtistSort = "Sort Artist:",
SortArtist = "Sort Artist:",
Artist = "Artist:",
ARTIST = "Artist:",
PerformerSortOrder = "Sort Artist:",
SortAlbumArtist = "Sort Album Artist:",
AlbumArtistSortOrder = "Sort Album Artist:",
AlbumArtistSort = "Sort Album Artist:",
AlbumSortOrder = "Sort Album:",
AlbumSort = "Sort Album:",
SortAlbum = "Sort Album:",
Album = "Album:",
ALBUM = "Album:",
AlbumArtist = "Album Artist:",
Genre = "Genre:",
GENRE = "Genre:",
TrackNumber = "Track Number:",
Year = "Year:",
Duration = "Duration:",
AudioBitrate = "Bitrate:",
AvgBitrate = "Average Bitrate:",
AudioSampleRate = "Sample Rate:",
SampleRate = "Sample Rate:",
AudioChannels = "Channels:"
}
for k, v in pairs(substitutions) do
metadata = metadata:gsub(tostring(k)..":", v, 1)
end
-- Separate the tag title from the tag data
local t={}
for str in string.gmatch(metadata , "([^"..":".."]+)") do
if str ~= "\n" then
table.insert(t, str)
else
table.insert(t, nil)
end
end
-- Add back semicolon to title, rejoin tag data if it happened to contain a semicolon
local title, tag_data = "", ""
if t[1] ~= nil then
title, tag_data = t[1]..":", table.concat(t, ":", 2)
end
return title, tag_data
end
function M:seek(job)
local h = cx.active.current.hovered
if h and h.url == job.file.url then
ya.manager_emit("peek", {
tostring(math.max(0, cx.active.preview.skip + job.units)),
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
local mediainfo_template = 'General;"\
$if(%Track%,Title: %Track%,)\
$if(%Track/Sort%,Sort Title: %Track/Sort%,)\
$if(%Title/Sort%,Sort Title: %Title/Sort%,)\
$if(%TITLESORT%,Sort Title: %TITLESORT%,)\
$if(%Performer%,Artist: %Performer%,)\
$if(%Performer/Sort%,Sort Artist: %Performer/Sort%,)\
$if(%ARTISTSORT%,Sort Artist: %ARTISTSORT%,)\
$if(%Album%,Album: %Album%,)\
$if(%Album/Sort%,Sort Album: %Album/Sort%)\
$if(%ALBUMSORT%,Sort Album: %ALBUMSORT%)\
$if(%Album/Performer%,Album Artist: %Album/Performer%)\
$if(%Album/Performer/Sort%,Sort Album Artist: %Album/Performer/Sort%)\
$if(%Genre%,Genre: %Genre%)\
$if(%Track/Position%,Track Number: %Track/Position%)\
$if(%Recorded_Date%,Year: %Recorded_Date%)\
$if(%Duration/String%,Duration: %Duration/String%)\
$if(%BitRate/String%,Bitrate: %BitRate/String%)\
"\
Audio;"Sample Rate: %SamplingRate%\
Channels: %Channel(s)%"\
'
-- Write the mediainfo template file into yazi's cache dir
local cache_dir = GetPath(tostring(cache))
fs.write(Url(cache_dir.."mediainfo.txt"), mediainfo_template)
local output = Command("exiftool")
:args({ "-b", "-CoverArt", "-Picture", tostring(job.file.url) })
:stdout(Command.PIPED)
:stderr(Command.PIPED)
:output()
if not output then
return 0
end
return fs.write(cache, output.stdout) and 1 or 2
end
return M

View File

@ -1,41 +0,0 @@
local function setup(_, opts)
local type = opts and opts.type or ui.Border.ROUNDED
local old_build = Tab.build
Tab.build = function(self, ...)
local bar = function(c, x, y)
if x <= 0 or x == self._area.w - 1 then
return ui.Bar(ui.Bar.TOP)
end
return ui.Bar(ui.Bar.TOP)
:area(
ui.Rect { x = x, y = math.max(0, y), w = ya.clamp(0, self._area.w - x, 1), h = math.min(1, self._area.h) }
)
:symbol(c)
end
local c = self._chunks
self._chunks = {
c[1]:padding(ui.Padding.y(1)),
c[2]:padding(ui.Padding(c[1].w > 0 and 0 or 1, c[3].w > 0 and 0 or 1, 1, 1)),
c[3]:padding(ui.Padding.y(1)),
}
local style = THEME.manager.border_style
self._base = ya.list_merge(self._base or {}, {
ui.Border(ui.Border.ALL):area(self._area):type(type):style(style),
ui.Bar(ui.Bar.RIGHT):area(self._chunks[1]):style(style),
ui.Bar(ui.Bar.LEFT):area(self._chunks[3]):style(style),
bar("", c[1].right - 1, c[1].y),
bar("", c[1].right - 1, c[1].bottom - 1),
bar("", c[2].right, c[2].y),
bar("", c[2].right, c[2].bottom - 1),
})
old_build(self, ...)
end
end
return { setup = setup }

View File

@ -1,208 +0,0 @@
local WIN = ya.target_family() == "windows"
local PATS = {
{ "[MT]", 6 }, -- Modified
{ "[AC]", 5 }, -- Added
{ "?$", 4 }, -- Untracked
{ "!$", 3 }, -- Ignored
{ "D", 2 }, -- Deleted
{ "U", 1 }, -- Updated
{ "[AD][AD]", 1 }, -- Updated
}
local function match(line)
local signs = line:sub(1, 2)
for _, p in ipairs(PATS) do
local path
if signs:find(p[1]) then
path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4)
path = WIN and path:gsub("/", "\\") or path
end
if not path then
elseif path:find("[/\\]$") then
return p[2] == 3 and 30 or p[2], path:sub(1, -2)
else
return p[2], path
end
end
end
local function root(cwd)
local is_worktree = function(url)
local file, head = io.open(tostring(url)), nil
if file then
head = file:read(8)
file:close()
end
return head == "gitdir: "
end
repeat
local next = cwd:join(".git")
local cha = fs.cha(next)
if cha and (cha.is_dir or is_worktree(next)) then
return tostring(cwd)
end
cwd = cwd:parent()
until not cwd
end
local function bubble_up(changed)
local new, empty = {}, Url("")
for k, v in pairs(changed) do
if v ~= 3 and v ~= 30 then
local url = Url(k):parent()
while url and url ~= empty do
local s = tostring(url)
new[s] = (new[s] or 0) > v and new[s] or v
url = url:parent()
end
end
end
return new
end
local function propagate_down(ignored, cwd, repo)
local new, rel = {}, cwd:strip_prefix(repo)
for k, v in pairs(ignored) do
if v == 30 then
if rel:starts_with(k) then
new[tostring(repo:join(rel))] = 30
elseif cwd == repo:join(k):parent() then
new[k] = 3
end
end
end
return new
end
local add = ya.sync(function(st, cwd, repo, changed)
st.dirs[cwd] = repo
st.repos[repo] = st.repos[repo] or {}
for k, v in pairs(changed) do
if v == 0 then
st.repos[repo][k] = nil
elseif v == 30 then
st.dirs[k] = ""
else
st.repos[repo][k] = v
end
end
ya.render()
end)
local remove = ya.sync(function(st, cwd)
local dir = st.dirs[cwd]
if not dir then
return
end
ya.render()
st.dirs[cwd] = nil
if not st.repos[dir] then
return
end
for _, r in pairs(st.dirs) do
if r == dir then
return
end
end
st.repos[dir] = nil
end)
local function setup(st, opts)
st.dirs = {}
st.repos = {}
opts = opts or {}
opts.order = opts.order or 1500
-- Chosen by ChatGPT fairly, PRs are welcome to adjust them
local t = THEME.git or {}
local styles = {
[6] = t.modified and ui.Style(t.modified) or ui.Style():fg("#ffa500"),
[5] = t.added and ui.Style(t.added) or ui.Style():fg("#32cd32"),
[4] = t.untracked and ui.Style(t.untracked) or ui.Style():fg("#a9a9a9"),
[3] = t.ignored and ui.Style(t.ignored) or ui.Style():fg("#696969"),
[2] = t.deleted and ui.Style(t.deleted) or ui.Style():fg("#ff4500"),
[1] = t.updated and ui.Style(t.updated) or ui.Style():fg("#1e90ff"),
}
local signs = {
[6] = t.modified_sign and t.modified_sign or "",
[5] = t.added_sign and t.added_sign or "",
[4] = t.untracked_sign and t.untracked_sign or "",
[3] = t.ignored_sign and t.ignored_sign or "",
[2] = t.deleted_sign and t.deleted_sign or "",
[1] = t.updated_sign and t.updated_sign or "U",
}
Linemode:children_add(function(self)
local url = self._file.url
local dir = st.dirs[tostring(url:parent())]
local change
if dir then
change = dir == "" and 3 or st.repos[dir][tostring(url):sub(#dir + 2)]
end
if not change or signs[change] == "" then
return ui.Line("")
elseif self._file:is_hovered() then
return ui.Line { ui.Span(" "), ui.Span(signs[change]) }
else
return ui.Line { ui.Span(" "), ui.Span(signs[change]):style(styles[change]) }
end
end, opts.order)
end
local function fetch(_, job)
local cwd = job.files[1].url:parent()
local repo = root(cwd)
if not repo then
remove(tostring(cwd))
return 1
end
local paths = {}
for _, f in ipairs(job.files) do
paths[#paths + 1] = tostring(f.url)
end
-- stylua: ignore
local output, err = Command("git")
:cwd(tostring(cwd))
:args({ "--no-optional-locks", "-c", "core.quotePath=", "status", "--porcelain", "-unormal", "--no-renames", "--ignored=matching" })
:args(paths)
:stdout(Command.PIPED)
:output()
if not output then
ya.err("Cannot spawn git command, error: " .. err)
return 0
end
local changed, ignored = {}, {}
for line in output.stdout:gmatch("[^\r\n]+") do
local sign, path = match(line)
if sign == 30 then
ignored[path] = sign
else
changed[path] = sign
end
end
if job.files[1].cha.is_dir then
ya.dict_merge(changed, bubble_up(changed))
ya.dict_merge(changed, propagate_down(ignored, cwd, Url(repo)))
else
ya.dict_merge(changed, propagate_down(ignored, cwd, Url(repo)))
end
for _, p in ipairs(paths) do
local s = p:sub(#repo + 2)
changed[s] = changed[s] or 0
end
add(tostring(cwd), repo, changed)
return 3
end
return { setup = setup, fetch = fetch }

View File

@ -1,76 +0,0 @@
local M = {}
function M:peek(job)
-- Set a fixed width of 55 characters for the preview
local preview_width = 55
local child = Command("glow")
:args({
"--style",
"dark",
"--width",
tostring(preview_width), -- Use fixed width instead of job.area.w
tostring(job.file.url),
})
:env("CLICOLOR_FORCE", "1")
:stdout(Command.PIPED)
:stderr(Command.PIPED)
:spawn()
if not child then
return require("code").peek(job)
end
local limit = job.area.h
local i, lines = 0, ""
repeat
local next, event = child:read_line()
if event == 1 then
return require("code").peek(job)
elseif event ~= 0 then
break
end
i = i + 1
if i > job.skip then
lines = lines .. next
end
until i >= job.skip + limit
child:start_kill()
if job.skip > 0 and i < job.skip + limit then
ya.manager_emit("peek", {
tostring(math.max(0, i - limit)),
only_if = job.file.url,
upper_bound = true
})
else
lines = lines:gsub("\t", string.rep(" ", PREVIEW.tab_size))
ya.preview_widgets(job, { ui.Text.parse(lines):area(job.area) })
end
end
function M:seek(job)
local h = cx.active.current.hovered
if not h or h.url ~= job.file.url then
return
end
local scroll_amount = 1
local scroll_offset = job.units
if job.key == "ctrl-e" then
scroll_offset = scroll_amount
elseif job.key == "ctrl-y" then
scroll_offset = -scroll_amount
else
scroll_offset = job.units
end
ya.manager_emit('peek', {
math.max(0, cx.active.preview.skip + scroll_offset),
only_if = job.file.url,
})
end
return M

View File

@ -1,57 +0,0 @@
local M = {}
function M:peek(job)
local child
local l = self.file.cha.len
if l == 0 then
child = Command("hexyl")
:args({
tostring(job.file.url),
})
:stdout(Command.PIPED)
:stderr(Command.PIPED)
:spawn()
else
child = Command("hexyl")
:args({
"--border",
"none",
"--terminal-width",
tostring(job.area.w),
tostring(job.file.url),
})
:stdout(Command.PIPED)
:stderr(Command.PIPED)
:spawn()
end
local limit = job.area.h
local i, lines = 0, ""
repeat
local next, event = child:read_line()
if event == 1 then
ya.err(tostring(event))
elseif event ~= 0 then
break
end
i = i + 1
if i > job.skip then
lines = lines .. next
end
until i >= job.skip + limit
child:start_kill()
if job.skip > 0 and i < job.skip + limit then
ya.manager_emit("peek", { math.max(0, i - limit), only_if = job.file.url, upper_bound = true })
else
lines = lines:gsub("\t", string.rep(" ", PREVIEW.tab_size))
ya.preview_widgets(job, { ui.Text.parse(lines):area(job.area) })
end
end
function M:seek(units)
require("code").seek(job, units)
end
return M

View File

@ -1,25 +0,0 @@
--- @sync entry
local function entry(st)
if st.old then
Tab.layout, st.old = st.old, nil
else
st.old = Tab.layout
Tab.layout = function(self)
local all = MANAGER.ratio.parent + MANAGER.ratio.current
self._chunks = ui.Layout()
:direction(ui.Layout.HORIZONTAL)
:constraints({
ui.Constraint.Ratio(MANAGER.ratio.parent, all),
ui.Constraint.Ratio(MANAGER.ratio.current, all),
ui.Constraint.Length(1),
})
:split(self._area)
end
end
ya.app_emit("resize", {})
end
local function enabled(st) return st.old ~= nil end
return { entry = entry, enabled = enabled }

View File

@ -1,24 +0,0 @@
--- @sync entry
local function entry(st)
if st.old then
Tab.layout, st.old = st.old, nil
else
st.old = Tab.layout
Tab.layout = function(self)
self._chunks = ui.Layout()
:direction(ui.Layout.HORIZONTAL)
:constraints({
ui.Constraint.Percentage(0),
ui.Constraint.Percentage(0),
ui.Constraint.Percentage(100),
})
:split(self._area)
end
end
ya.app_emit("resize", {})
end
local function enabled(st) return st.old ~= nil end
return { entry = entry, enabled = enabled }

View File

@ -1,19 +0,0 @@
Copyright (c) 2024 Lauri Niskanen
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.

View File

@ -1,22 +0,0 @@
# mediainfo.yazi
This is a Yazi plugin for previewing media files. The preview shows thumbnail
using `ffmpegthumbnailer` if available and media metadata using `mediainfo`.
## Installation
Install the plugin:
```
ya pack -a Ape/mediainfo
```
Create `~/.config/yazi/yazi.toml` and add:
```
[plugin]
prepend_previewers = [
{ mime = "{image,audio,video}/*", run = "mediainfo"},
{ mime = "application/x-subrip", run = "mediainfo"},
]
```

View File

@ -1,111 +0,0 @@
local skip_labels = {
["Complete name"] = true,
["CompleteName_Last"] = true,
["Unique ID"] = true,
["File size"] = true,
["Format/Info"] = true,
["Codec ID/Info"] = true,
["MD5 of the unencoded content"] = true,
}
local M = {}
function M:peek()
local image_height = 0
if self:preload() == 1 then
local cache = ya.file_cache(self)
if cache and fs.cha(cache).length > 0 then
image_height = ya.image_show(cache, self.area).h
end
end
local cmd = "mediainfo"
local output, code = Command(cmd)
:args({ tostring(self.file.url) })
:stdout(Command.PIPED)
:output()
local lines = {}
if output then
local i = 0
for str in output.stdout:gmatch("[^\n]*") do
local label, value = str:match("(.*[^ ]) +: (.*)")
local line
if label then
if not skip_labels[label] then
line = ui.Line({
ui.Span(label .. ": "):bold(),
ui.Span(value),
})
end
elseif str ~= "General" then
line = ui.Line({ ui.Span(str):underline() })
end
if line then
if i >= self.skip then
table.insert(lines, line)
end
local max_width = math.max(1, self.area.w - 3)
i = i + math.max(1, math.ceil(line:width() / max_width))
end
end
else
local error = string.format("Spawn `%s` command returns %s", cmd, code)
table.insert(lines, ui.Line(error))
end
ya.preview_widgets(self, {
ui.Paragraph(
ui.Rect({
x = self.area.x,
y = self.area.y + image_height,
w = self.area.w,
h = self.area.h - image_height,
}),
lines
):wrap(ui.Paragraph.WRAP),
})
end
function M:seek(units)
local h = cx.active.current.hovered
if h and h.url == self.file.url then
local step = math.floor(units * self.area.h / 10)
ya.manager_emit("peek", {
math.max(0, cx.active.preview.skip + step),
only_if = self.file.url,
})
end
end
function M:preload()
local cache = ya.file_cache(self)
if not cache or fs.cha(cache) then
return 1
end
local cmd = "ffmpegthumbnailer"
local child, code = Command(cmd):args({
"-q", "6",
"-c", "jpeg",
"-i", tostring(self.file.url),
"-o", tostring(cache),
"-t", "5",
"-s", tostring(PREVIEW.max_width),
}):spawn()
if not child then
ya.err(string.format("spawn `%s` command returns %s", cmd, code))
return 0
end
local status = child:wait()
return status and status.success and 1 or 2
end
return M

View File

@ -1,59 +0,0 @@
local M = {}
function M:peek()
local child = Command("mlr")
:args({
"--icsv",
"--opprint",
"-C",
"--key-color",
"darkcyan",
"--value-color",
"grey70",
"cat",
tostring(self.file.url),
})
:stdout(Command.PIPED)
:stderr(Command.PIPED)
:spawn()
local limit = self.area.h
local i, lines = 0, ""
repeat
local next, event = child:read_line()
if event == 1 then
ya.err(tostring(event))
elseif event ~= 0 then
break
end
i = i + 1
if i > self.skip then
lines = lines .. next
end
until i >= self.skip + limit
child:start_kill()
if self.skip > 0 and i < self.skip + limit then
ya.manager_emit(
"peek",
{ tostring(math.max(0, i - limit)), only_if = tostring(self.file.url), upper_bound = "" }
)
else
lines = lines:gsub("\t", string.rep(" ", PREVIEW.tab_size))
ya.preview_widgets(self, { ui.Paragraph.parse(self.area, lines) })
end
end
function M:seek(units)
local h = cx.active.current.hovered
if h and h.url == self.file.url then
local step = math.floor(units * self.area.h / 10)
ya.manager_emit("peek", {
tostring(math.max(0, cx.active.preview.skip + step)),
only_if = tostring(self.file.url),
})
end
end
return M

View File

@ -1,58 +0,0 @@
local M = {}
function M:peek(job)
local child = Command("nbpreview")
:args({
-- DO NOT CHANGE --
"--no-paging",
"--nerd-font",
"--decorated",
-- OPTIONAL CHANGES --
"--no-files",
"--unicode",
"--color",
"--images",
-- SPECIAL CUSTOMIZATIONS --
"--color-system=standard",
"--theme=ansi_dark",
tostring(job.file.url),
})
:stdout(Command.PIPED)
:stderr(Command.PIPED)
:spawn()
if not child then
return require("code"):peek(job)
end
local limit = job.area.h
local i, lines = 0, ""
repeat
local next, event = child:read_line()
if event == 1 then
return require("code"):peek(job)
elseif event ~= 0 then
break
end
i = i + 1
if i > job.skip then
lines = lines .. next
end
until i >= job.skip + limit
child:start_kill()
if job.skip > 0 and i < job.skip + limit then
ya.manager_emit("peek", { math.max(0, i - limit), only_if = job.file.url, upper_bound = true })
else
lines = lines:gsub("\t", string.rep(" ", PREVIEW.tab_size))
ya.preview_widgets(job, { ui.Text.parse(lines):area(job.area) })
end
end
function M:seek(job)
require("code"):seek(job)
end
return M

View File

@ -1,145 +0,0 @@
local M = {}
function M:peek(job)
local child = Command("ouch")
:args({ "l", "-t", "-y", tostring(job.file.url) })
:stdout(Command.PIPED)
:stderr(Command.PIPED)
:spawn()
local limit = job.area.h
local file_name = string.match(tostring(job.file.url), ".*[/\\](.*)")
local lines = string.format("📁 \x1b[2m%s\x1b[0m\n", file_name)
local num_lines = 1
local num_skip = 0
repeat
local line, event = child:read_line()
if event == 1 then
ya.err(tostring(event))
elseif event ~= 0 then
break
end
if line:find('Archive', 1, true) ~= 1 and line:find('[INFO]', 1, true) ~= 1 then
if num_skip >= job.skip then
lines = lines .. line
num_lines = num_lines + 1
else
num_skip = num_skip + 1
end
end
until num_lines >= limit
child:start_kill()
if job.skip > 0 and num_lines < limit then
ya.manager_emit(
"peek",
{ tostring(math.max(0, job.skip - (limit - num_lines))), only_if = tostring(job.file.url), upper_bound = "" }
)
else
ya.preview_widgets(job, { ui.Text(lines):area(job.area) })
end
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", {
math.max(0, cx.active.preview.skip + step),
only_if = tostring(job.file.url),
})
end
end
-- Check if file exists
local function file_exists(name)
local f = io.open(name, "r")
if f ~= nil then
io.close(f)
return true
else
return false
end
end
-- Get the files that need to be compressed and infer a default archive name
local get_compression_target = ya.sync(function()
local tab = cx.active
local default_name
local paths = {}
if #tab.selected == 0 then
if tab.current.hovered then
local name = tab.current.hovered.name
default_name = name
table.insert(paths, name)
else
return
end
else
default_name = tab.current.cwd:name()
for _, url in pairs(tab.selected) do
table.insert(paths, tostring(url))
end
-- The compression targets are aquired, now unselect them
ya.manager_emit("escape", {})
end
return paths, default_name
end)
local function invoke_compress_command(paths, name)
local cmd_output, err_code = Command("ouch")
:args({ "c", "-y" })
:args(paths)
:arg(name)
:stderr(Command.PIPED)
:output()
if err_code ~= nil then
ya.notify({
title = "Failed to run ouch command",
content = "Status: " .. err_code,
timeout = 5.0,
level = "error",
})
elseif not cmd_output.status.success then
ya.notify({
title = "Compression failed: status code " .. cmd_output.status.code,
content = cmd_output.stderr,
timeout = 5.0,
level = "error",
})
end
end
function M:entry(job)
local default_fmt = job.args[1]
ya.manager_emit("escape", { visual = true })
-- Get the files that need to be compressed and infer a default archive name
local paths, default_name = get_compression_target()
-- Get archive name from user
local output_name, name_event = ya.input({
title = "Create archive:",
value = default_name .. "." .. default_fmt,
position = { "top-center", y = 3, w = 40 },
})
if name_event ~= 1 then
return
end
-- Get confirmation if file exists
if file_exists(output_name) then
local confirm, confirm_event = ya.input({
title = "Overwrite " .. output_name .. "? (y/N)",
position = { "top-center", y = 3, w = 40 },
})
if not (confirm_event == 1 and confirm:lower() == "y") then
return
end
end
invoke_compress_command(paths, output_name)
end
return M

View File

@ -1,326 +0,0 @@
-- stylua: ignore
local MOTIONS_AND_OP_KEYS = {
{ on = "0" }, { on = "1" }, { on = "2" }, { on = "3" }, { on = "4" },
{ on = "5" }, { on = "6" }, { on = "7" }, { on = "8" }, { on = "9" },
-- commands
{ on = "d" }, { on = "v" }, { on = "y" }, { on = "x" },
-- tab commands
{ on = "t" }, { on = "L" }, { on = "H" }, { on = "w" },
{ on = "W" }, { on = "<" }, { on = ">" }, { on = "~" },
-- movement
{ on = "g" }, { on = "j" }, { on = "k" }, { on = "<Down>" }, { on = "<Up>" }
}
-- stylua: ignore
local MOTION_KEYS = {
{ on = "0" }, { on = "1" }, { on = "2" }, { on = "3" }, { on = "4" },
{ on = "5" }, { on = "6" }, { on = "7" }, { on = "8" }, { on = "9" },
-- movement
{ on = "g" }, { on = "j" }, { on = "k" }
}
-- stylua: ignore
local DIRECTION_KEYS = {
{ on = "j" }, { on = "k" }, { on = "<Down>" }, { on = "<Up>" },
-- tab movement
{ on = "t" }
}
local SHOW_NUMBERS_ABSOLUTE = 0
local SHOW_NUMBERS_RELATIVE = 1
local SHOW_NUMBERS_RELATIVE_ABSOLUTE = 2
-----------------------------------------------
----------------- R E N D E R -----------------
-----------------------------------------------
local render_motion_setup = ya.sync(function(_)
ya.render()
Status.motion = function() return ui.Span("") end
Status.children_redraw = function(self, side)
local lines = {}
if side == self.RIGHT then
lines[1] = self:motion(self)
end
for _, c in ipairs(side == self.RIGHT and self._right or self._left) do
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)
end
return ui.Line(lines)
end
-- TODO: check why it doesn't work line this
-- Status:children_add(function() return ui.Span("") end, 1000, Status.RIGHT)
end)
local render_motion = ya.sync(function(_, motion_num, motion_cmd)
ya.render()
Status.motion = function(self)
if not motion_num then
return ui.Span("")
end
local style = self:style()
local motion_span
if not motion_cmd then
motion_span = ui.Span(string.format(" %3d ", motion_num))
else
motion_span = ui.Span(string.format(" %3d%s ", motion_num, motion_cmd))
end
return ui.Line {
ui.Span(THEME.status.separator_open):fg(style.main.bg),
motion_span:style(style.main),
ui.Span(THEME.status.separator_close):fg(style.main.bg),
ui.Span(" "),
}
end
end)
local render_numbers = ya.sync(function(_, mode)
ya.render()
Entity.number = function(_, index, file, hovered)
local idx
if mode == SHOW_NUMBERS_RELATIVE then
idx = math.abs(hovered - index)
elseif mode == SHOW_NUMBERS_ABSOLUTE then
idx = file.idx
else -- SHOW_NUMBERS_RELATIVE_ABSOLUTE
if hovered == index then
idx = file.idx
else
idx = math.abs(hovered - index)
end
end
-- emulate vim's hovered offset
if idx >= 100 then
return ui.Span(string.format("%4d ", idx))
elseif hovered == index then
return ui.Span(string.format("%3d ", idx))
else
return ui.Span(string.format(" %3d ", idx))
end
end
Current.redraw = function(self)
local files = self._folder.window
if #files == 0 then
return self:empty()
end
local hovered_index
for i, f in ipairs(files) do
if f:is_hovered() then
hovered_index = i
break
end
end
local entities, linemodes = {}, {}
for i, f in ipairs(files) do
linemodes[#linemodes + 1] = Linemode:new(f):redraw()
local entity = Entity:new(f)
entities[#entities + 1] = ui.Line({ Entity:number(i, f, hovered_index), entity:redraw() }):style(entity:style())
end
return {
ui.List(entities):area(self._area),
ui.Text(linemodes):area(self._area):align(ui.Text.RIGHT),
}
end
end)
local function render_clear() render_motion() end
-----------------------------------------------
--------- C O M M A N D P A R S E R ---------
-----------------------------------------------
local get_keys = ya.sync(function(state) return state._only_motions and MOTION_KEYS or MOTIONS_AND_OP_KEYS end)
local function normal_direction(dir)
if dir == "<Down>" then
return "j"
elseif dir == "<Up>" then
return "k"
end
return dir
end
local function get_cmd(first_char, keys)
local last_key
local lines = first_char or ""
while true do
render_motion(tonumber(lines))
local key = ya.which { cands = keys, silent = true }
if not key then
return nil, nil, nil
end
last_key = keys[key].on
if not tonumber(last_key) then
last_key = normal_direction(last_key)
break
end
lines = lines .. last_key
end
render_motion(tonumber(lines), last_key)
-- command direction
local direction
if last_key == "g" or last_key == "v" or last_key == "d" or last_key == "y" or last_key == "x" then
DIRECTION_KEYS[#DIRECTION_KEYS + 1] = {
on = last_key,
}
local direction_key = ya.which { cands = DIRECTION_KEYS, silent = true }
if not direction_key then
return nil, nil, nil
end
direction = DIRECTION_KEYS[direction_key].on
direction = normal_direction(direction)
end
return tonumber(lines), last_key, direction
end
local function is_tab_command(command)
local tab_commands = { "t", "L", "H", "w", "W", "<", ">", "~" }
for _, cmd in ipairs(tab_commands) do
if command == cmd then
return true
end
end
return false
end
local get_active_tab = ya.sync(function(_) return cx.tabs.idx end)
-----------------------------------------------
---------- E N T R Y / S E T U P ----------
-----------------------------------------------
return {
entry = function(_, job)
local initial_value
local args = job.args
-- this is checking if the argument is a valid number
if #args > 0 then
initial_value = tostring(tonumber(args[1]))
if initial_value == "nil" then
return
end
end
local lines, cmd, direction = get_cmd(initial_value, get_keys())
if not lines or not cmd then
-- command was cancelled
render_clear()
return
end
if cmd == "g" then
if direction == "g" then
ya.manager_emit("arrow", { -99999999 })
ya.manager_emit("arrow", { lines - 1 })
render_clear()
return
elseif direction == "j" then
cmd = "j"
elseif direction == "k" then
cmd = "k"
elseif direction == "t" then
ya.manager_emit("tab_switch", { lines - 1 })
render_clear()
return
else
-- no valid direction
render_clear()
return
end
end
if cmd == "j" then
ya.manager_emit("arrow", { lines })
elseif cmd == "k" then
ya.manager_emit("arrow", { -lines })
elseif is_tab_command(cmd) then
if cmd == "t" then
for _ = 1, lines do
ya.manager_emit("tab_create", {})
end
elseif cmd == "H" then
ya.manager_emit("tab_switch", { -lines, relative = true })
elseif cmd == "L" then
ya.manager_emit("tab_switch", { lines, relative = true })
elseif cmd == "w" then
ya.manager_emit("tab_close", { lines - 1 })
elseif cmd == "W" then
local curr_tab = get_active_tab()
local del_tab = curr_tab + lines - 1
for _ = curr_tab, del_tab do
ya.manager_emit("tab_close", { curr_tab - 1 })
end
ya.manager_emit("tab_switch", { curr_tab - 1 })
elseif cmd == "<" then
ya.manager_emit("tab_swap", { -lines })
elseif cmd == ">" then
ya.manager_emit("tab_swap", { lines })
elseif cmd == "~" then
local jump = lines - get_active_tab()
ya.manager_emit("tab_swap", { jump })
end
else
ya.manager_emit("visual_mode", {})
-- invert direction when user specifies it
if direction == "k" then
ya.manager_emit("arrow", { -lines })
elseif direction == "j" then
ya.manager_emit("arrow", { lines })
else
ya.manager_emit("arrow", { lines - 1 })
end
ya.manager_emit("escape", {})
if cmd == "d" then
ya.manager_emit("remove", {})
elseif cmd == "y" then
ya.manager_emit("yank", {})
elseif cmd == "x" then
ya.manager_emit("yank", { cut = true })
end
end
render_clear()
end,
setup = function(state, args)
if not args then
return
end
-- initialize state variables
state._only_motions = args["only_motions"] or false
if args["show_motion"] then
render_motion_setup()
end
if args["show_numbers"] == "absolute" then
render_numbers(SHOW_NUMBERS_ABSOLUTE)
elseif args["show_numbers"] == "relative" then
render_numbers(SHOW_NUMBERS_RELATIVE)
elseif args["show_numbers"] == "relative_absolute" then
render_numbers(SHOW_NUMBERS_RELATIVE_ABSOLUTE)
end
end,
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Rolv Apneseth
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.

View File

@ -1,101 +0,0 @@
# starship.yazi
Starship prompt plugin for [Yazi](https://github.com/sxyazi/yazi)
<https://github.com/Rolv-Apneseth/starship.yazi/assets/69486699/f7314687-5cb1-4d66-8d9d-cca960ba6716>
## Requirements
- [Yazi](https://github.com/sxyazi/yazi)
- [starship](https://github.com/starship/starship)
## Installation
```bash
ya pack -a Rolv-Apneseth/starship
```
### Manual
```sh
# Linux / MacOS
git clone https://github.com/Rolv-Apneseth/starship.yazi.git ~/.config/yazi/plugins/starship.yazi
# Windows
git clone https://github.com/Rolv-Apneseth/starship.yazi.git %AppData%\yazi\config\plugins\starship.yazi
```
## Usage
Add this to `~/.config/yazi/init.lua`:
```lua
require("starship"):setup()
```
If you wish to define a custom config file for `starship` to use, you can pass in a path
to the setup function like this:
```lua
starship:setup({ config_file = "/home/rolv/.config/starship_secondary.toml" })
```
Make sure you have [starship](https://github.com/starship/starship) installed and in your `PATH`.
## Extra
If you use a `starship` theme with a background colour, it might look a bit to cramped on just the one line `Yazi` gives the header by default. To fix this, you can add this to your `init.lua`:
<details>
<summary>Click to expand</summary>
```lua
local old_build = Tab.build
Tab.build = function(self, ...)
local bar = function(c, x, y)
if x <= 0 or x == self._area.w - 1 then
return ui.Bar(ui.Rect.default, ui.Bar.TOP)
end
return ui.Bar(
ui.Rect({
x = x,
y = math.max(0, y),
w = ya.clamp(0, self._area.w - x, 1),
h = math.min(1, self._area.h),
}),
ui.Bar.TOP
):symbol(c)
end
local c = self._chunks
self._chunks = {
c[1]:padding(ui.Padding.y(1)),
c[2]:padding(ui.Padding(c[1].w > 0 and 0 or 1, c[3].w > 0 and 0 or 1, 1, 1)),
c[3]:padding(ui.Padding.y(1)),
}
local style = THEME.manager.border_style
self._base = ya.list_merge(self._base or {}, {
-- Enable for full border
--[[ ui.Border(self._area, ui.Border.ALL):type(ui.Border.ROUNDED):style(style), ]]
ui.Bar(self._chunks[1], ui.Bar.RIGHT):style(style),
ui.Bar(self._chunks[3], ui.Bar.LEFT):style(style),
bar("┬", c[1].right - 1, c[1].y),
bar("┴", c[1].right - 1, c[1].bottom - 1),
bar("┬", c[2].right, c[2].y),
bar("┴", c[2].right, c[1].bottom - 1),
})
old_build(self, ...)
end
```
</details>
> [!NOTE]
> This works by overriding your `Tab.build` function so make sure this is the only place you're doing that in your config. For example, this would be incompatible with the [full-border plugin](https://github.com/yazi-rs/plugins/tree/main/full-border.yazi)
## Thanks
- [sxyazi](https://github.com/sxyazi) for providing the code for this plugin and the demo video [in this comment](https://github.com/sxyazi/yazi/issues/767#issuecomment-1977082834)

View File

@ -1,78 +0,0 @@
local save = ya.sync(function(st, cwd, output)
if cx.active.current.cwd == Url(cwd) then
st.output = output
ya.render()
end
end)
-- Helper function for accessing the `config_file` state variable
---@return string
local get_config_file = ya.sync(function(st)
return st.config_file
end)
return {
---User arguments for setup method
---@class SetupArgs
---@field config_file string Absolute path to a starship config file
--- Setup plugin
--- @param st table State
--- @param args SetupArgs|nil
setup = function(st, args)
-- Replace default header widget
Header:children_remove(1, Header.LEFT)
Header:children_add(function()
return ui.Line.parse(st.output or "")
end, 1000, Header.LEFT)
-- Check for custom starship config file
if args ~= nil and args.config_file ~= nil then
local url = Url(args.config_file)
if url.is_regular then
local config_file = args.config_file
-- Manually replace '~' and '$HOME' at the start of the path with the OS environment variable
local home = os.getenv("HOME")
if home then
home = tostring(home)
config_file = config_file:gsub("^~", home):gsub("^$HOME", home)
end
st.config_file = config_file
end
end
-- Pass current working directory and custom config path (if specified) to the plugin's entry point
---Callback for subscribers to update the prompt
local callback = function()
local cwd = cx.active.current.cwd
if st.cwd ~= cwd then
st.cwd = cwd
ya.manager_emit("plugin", {
st._id,
args = ya.quote(tostring(cwd), true),
})
end
end
-- Subscribe to events
ps.sub("cd", callback)
ps.sub("tab", callback)
end,
entry = function(_, args)
local command = Command("starship"):arg("prompt"):cwd(args[1]):env("STARSHIP_SHELL", "")
-- Point to custom starship config
local config_file = get_config_file()
if config_file then
command = command:env("STARSHIP_CONFIG", config_file)
end
local output = command:output()
if output then
save(args[1], output.stdout:gsub("^%s+", ""))
end
end,
}

View File

@ -1,46 +0,0 @@
local M = {}
function M:peek(job)
local child = Command("transmission-show")
:args({
tostring(job.file.url),
})
:stdout(Command.PIPED)
:stderr(Command.PIPED)
:spawn()
if not child then
return require("code"):peek(job)
end
local limit = job.area.h
local i, lines = 0, ""
repeat
local next, event = child:read_line()
if event == 1 then
return require("code"):peek(job)
elseif event ~= 0 then
break
end
i = i + 1
if i > job.skip then
lines = lines .. next
end
until i >= job.skip + limit
child:start_kill()
if job.skip > 0 and i < job.skip + limit then
ya.manager_emit("peek", { math.max(0, i - limit), only_if = job.file.url, upper_bound = true })
else
lines = lines:gsub("\t", string.rep(" ", PREVIEW.tab_size))
ya.preview_widgets(job, { ui.Text.parse(lines):area(job.area) })
end
end
function M:seek(job)
require("code"):seek(job)
end
return M

View File

@ -1,72 +0,0 @@
-- function to get paths of selected elements or current directory
-- of no elements are selected
local get_paths = ya.sync(function()
local paths = {}
-- get selected files
for _, u in pairs(cx.active.selected) do
paths[#paths + 1] = tostring(u)
end
-- if no files are selected, get current directory
if #paths == 0 then
if cx.active.current.cwd then
paths[1] = tostring(cx.active.current.cwd)
else
ya.err("what-size would return nil paths")
end
end
return paths
end)
-- Function to get total size from du output
local get_total_size = function(s)
local lines = {}
for line in s:gmatch("[^\n]+") do lines[#lines + 1] = line end
local last_line = lines[#lines]
local last_line_parts = {}
for part in last_line:gmatch("%S+") do last_line_parts[#last_line_parts + 1] = part end
local total_size = last_line_parts[1]
return total_size
end
-- Function to format file size
local function format_size(size)
local units = { "B", "KB", "MB", "GB", "TB" }
local unit_index = 1
while size > 1024 and unit_index < #units do
size = size / 1024
unit_index = unit_index + 1
end
return string.format("%.2f %s", size, units[unit_index])
end
return {
entry = function(self, job)
-- defaults not to use clipboard, use it only if required by the user
local clipboard = job.args.clipboard or job.args[1] == '-c'
local items = get_paths()
local cmd = "du"
local output, err = Command(cmd):arg("-scb"):args(items):output()
if not output then
ya.err("Failed to run diff, error: " .. err)
else
local total_size = get_total_size(output.stdout)
local formatted_size = format_size(tonumber(total_size))
local notification_content = "Total size: " .. formatted_size
if clipboard then
ya.clipboard(formatted_size)
notification_content = notification_content .. "\nCopied to clipboard."
end
ya.notify {
title = "What size",
content = notification_content,
timeout = 5,
}
end
end,
}

File diff suppressed because it is too large Load Diff

View File

@ -143,6 +143,8 @@ bindkey -s '^f' '^utmux neww tmux-sessionizer\n'
eval "$(starship init zsh)" eval "$(starship init zsh)"
eval "$(fzf --zsh)" eval "$(fzf --zsh)"
eval "$(uv generate-shell-completion zsh)"
eval "$(uvx --generate-shell-completion zsh)"
[[ -r ~/.local/share/zsh/plugins/znap/znap.zsh ]] || [[ -r ~/.local/share/zsh/plugins/znap/znap.zsh ]] ||
git clone --depth 1 -- \ git clone --depth 1 -- \

0
dotter Normal file → Executable file
View File

0
dotter.arm Normal file → Executable file
View File

0
dotter.exe Normal file → Executable file
View File