", run = "arrow 1", desc = "Move cursor down" },
diff --git a/yazi/package.toml b/yazi/package.toml
index 3bbe63b..7be5ddb 100644
--- a/yazi/package.toml
+++ b/yazi/package.toml
@@ -1,27 +1,27 @@
[[plugin.deps]]
use = "yazi-rs/plugins:git"
-rev = "5186af7"
-hash = "771f18427fb75fb19990ce602bb322f4"
+rev = "d1c8baa"
+hash = "63b6c222bf2103b3023389dde5e2ecfe"
[[plugin.deps]]
use = "yazi-rs/plugins:smart-enter"
-rev = "5186af7"
-hash = "aef2b1a805b80cce573bb766f1459d88"
+rev = "d1c8baa"
+hash = "56fdabc96fc1f4d53c96eb884b02a5be"
[[plugin.deps]]
use = "h-hg/yamb"
-rev = "3f7c51f"
-hash = "e11b980e5635f0fbabd80931b1a1347e"
+rev = "22af003"
+hash = "7cc42012a7c2099f80064d228feb8d44"
[[plugin.deps]]
use = "KKV9/compress"
-rev = "60b24af"
-hash = "ee025be766240cc98e671754ac836da3"
+rev = "c264639"
+hash = "e17c11b605d989568a1d1741ca17c584"
[[plugin.deps]]
use = "llanosrocas/yaziline"
-rev = "1342efe"
-hash = "a84a339953a568fee1d8beb63e6dca73"
+rev = "e79b067"
+hash = "f590c5b7d0730e8d6023b1b34ddf7ead"
[flavor]
deps = []
diff --git a/yazi/plugins/compress.yazi/README.md b/yazi/plugins/compress.yazi/README.md
index 385fe38..ae1f329 100644
--- a/yazi/plugins/compress.yazi/README.md
+++ b/yazi/plugins/compress.yazi/README.md
@@ -1,48 +1,173 @@
-# ~~archive.yazi~~ compress.yazi
+🗜️ compress.yazi
+
+ A blazing fast, flexible archive plugin for Yazi
+ Effortlessly compress your files and folders with style!
+
-A Yazi plugin that compresses selected files to an archive. Supporting yazi versions 0.2.5 and up.
+---
-## Supported file types
+## 📖 Table of Contents
-| 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 |
+- [Features](#-features)
+- [Supported File Types](#-supported-file-types)
+- [Installation](#%EF%B8%8F-installation)
+- [Keymap Example](#-keymap-example)
+- [Usage](#%EF%B8%8F-usage)
+- [Flags](#%EF%B8%8F-flags)
+- [Tips](#-tips)
+- [Credits](#-credits)
+---
-**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.
+## 🚀 Features
+- 🗂️ **Multi-format support:** zip, 7z, rar, tar, tar.gz, tar.xz, tar.bz2, tar.zst, tar.lz4, tar.lha
+- 🌍 **Cross-platform:** Works on Unix & Windows
+- 🔒 **Password protection:** Secure your archives (zip/7z/rar)
+- 🛡️ **Header encryption:** Hide file lists (7z/rar)
+- ⚡ **Compression level:** Choose your balance of speed vs. size
+- 🛑 **Overwrite safety:** Never lose files by accident
+- 🎯 **Seamless Yazi integration:** Fast, native-like UX
-## Install
+---
+
+## 📦 Supported File Types
+
+| Extension | Default Command | 7z Command | Bsdtar Command (Win10+ & Unix) |
+| ------------- | ----------------- | -------------- | ------------------------------ |
+| `.zip` | `zip -r` | `7z a -tzip` | `tar -caf` |
+| `.7z` | `7z a` | `7z a` | |
+| `.rar` | `rar a` | | |
+| `.tar` | `tar rpf` | | `tar rpf` |
+| `.tar.gz` | `tar rpf + gzip` | `7z a -tgzip` | `tar -czf` |
+| `.tar.xz` | `tar rpf + xz` | `7z a -txz` | `tar -cJf` |
+| `.tar.bz2` | `tar rpf + bzip2` | `7z a -tbzip2` | `tar -cjf` |
+| `.tar.zst` | `tar rpf + zstd` | | `tar --zstd -cf` |
+| `.tar.lz4` | `tar rpf + lz4` | | |
+| `.tar.lha` | `tar rpf + lha` | | |
+
+---
+
+## ⚡️ Installation
```bash
-# For Unix platforms
+# Unix
git clone https://github.com/KKV9/compress.yazi.git ~/.config/yazi/plugins/compress.yazi
-## For Windows
+# Windows (CMD, not PowerShell!)
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
+ya pkg add KKV9/compress
```
-- Add this to your `keymap.toml`:
+---
+
+### 🔧 Extras (Windows)
+
+To enable additional compression formats and features on Windows, follow these steps:
+
+1. **Install [7-Zip](https://www.7-zip.org/):**
+ Add `C:\Program Files\7-Zip` to your `PATH`.
+ This enables support for `.7z` archives and password-protected `.zip` files.
+
+2. **Alternative: Install [Nanazip](https://github.com/M2Team/NanaZip):**
+ A modern alternative to 7-Zip with similar functionality and extra features.
+
+3. **Install [WinRAR](https://www.win-rar.com/download.html):**
+ Add `C:\Program Files\WinRAR` to your `PATH`.
+ This enables support for `.rar` archives.
+
+4. **Install Additional Tools:**
+ To use formats like `lha`, `lz4`, `gzip`, etc., install their respective tools and ensure they are added to your `PATH`.
+
+---
+
+## 🎹 Keymap Example
+
+Add this to your `keymap.toml`:
+
```toml
-[[manager.prepend_keymap]]
-on = [ "c", "a" ]
+[[mgr.prepend_keymap]]
+on = [ "c", "a", "a" ]
run = "plugin compress"
desc = "Archive selected files"
+
+[[mgr.prepend_keymap]]
+on = [ "c", "a", "p" ]
+run = "plugin compress -p"
+desc = "Archive selected files (password)"
+
+[[mgr.prepend_keymap]]
+on = [ "c", "a", "h" ]
+run = "plugin compress -ph"
+desc = "Archive selected files (password+header)"
+
+[[mgr.prepend_keymap]]
+on = [ "c", "a", "l" ]
+run = "plugin compress -l"
+desc = "Archive selected files (compression level)"
+
+[[mgr.prepend_keymap]]
+on = [ "c", "a", "u" ]
+run = "plugin compress -phl"
+desc = "Archive selected files (password+header+level)"
```
-## 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.
+## 🛠️ Usage
+
+1. **Select files/folders** in Yazi.
+2. Press c a to open the archive dialog.
+3. Choose:
+ - a for a standard archive
+ - p for password protection (zip/7z/rar)
+ - h to encrypt header (7z/rar)
+ - l to set compression level (all compression algorithims)
+ - u for all options together
+4. **Type a name** for your archive (or leave blank for suggested name).
+5. **Enter password** and/or **compression level** if prompted.
+6. **Overwrite protect** if a file already exists, the new file will be given a suffix _#.
+7. Enjoy your shiny new archive!
+
+---
+
+## 🏳️🌈 Flags
+
+- Combine flags for more power!
+- when separating flags with spaces, make sure to single quote them (eg., `'-ph rar'`)
+- `-p` Password protect (zip/7z/rar)
+- `-h` Encrypt header (7z/rar)
+- `-l` Set compression level (all compression algorithims)
+- `` Specify a default extention (eg., `7z`, `tar.gz`)
+
+#### Combining multiple flags:
+```toml
+[[mgr.prepend_keymap]]
+on = [ "c", "a", "7" ]
+run = "plugin compress '-ph 7z'"
+desc = "Archive selected files to 7z (password+header)"
+[[mgr.prepend_keymap]]
+on = [ "c", "a", "r" ]
+run = "plugin compress '-p -l rar'"
+desc = "Archive selected files to rar (password+level)"
+```
+
+---
+
+## 💡 Tips
+
+- The file extension **must** match a supported type.
+- The required compression tool **must** be installed and in your `PATH` (7zip/rar etc.).
+- If no extention is provided, the default extention (zip) will be appended automatically.
+
+---
+
+## 📣 Credits
+
+Made with ❤️ for [Yazi](https://github.com/sxyazi/yazi) by [KKV9](https://github.com/KKV9).
+Contributions are welcome! Feel free to submit a pull request.
+
+---
diff --git a/yazi/plugins/compress.yazi/main.lua b/yazi/plugins/compress.yazi/main.lua
index 333587f..c3c4241 100644
--- a/yazi/plugins/compress.yazi/main.lua
+++ b/yazi/plugins/compress.yazi/main.lua
@@ -1,228 +1,496 @@
--- 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"
+-- Define flags and strings
+local is_password, is_encrypted, is_level, cmd_password, cmd_level, default_extension = false, false, false, "", "", "zip"
--- 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)
+-- Function to check valid filename
+local function is_valid_filename(name)
+ -- Trim whitespace from both ends
+ name = name:match("^%s*(.-)%s*$")
+ if name == "" then
+ return false
+ end
+ if is_windows then
+ -- Windows forbidden chars and reserved names
+ if name:find('[<>:"/\\|%?%*]') then
+ return false
+ end
+ else
+ -- Unix forbidden chars
+ if name:find("/") or name:find("%z") then
+ return false
+ end
+ end
+ return true
+end
--- Check if archive command is available
+-- Function to send notifications
+local function notify_error(message, urgency)
+ ya.notify(
+ {
+ title = "Archive",
+ content = message,
+ level = urgency,
+ timeout = 5
+ }
+ )
+end
+
+-- Function to check if 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
+ 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
+-- Function to change command arrays --> string -- Use first command available or first command
+local function find_command_name(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
+-- Function to append filename to it's parent directory url
local function combine_url(path, file)
- path, file = Url(path), Url(file)
- return tostring(path:join(file))
+ path, file = Url(path), Url(file)
+ return tostring(path:join(file))
end
+-- Function to make a table of selected or hovered files: 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, names, tostring(tab.current.cwd)
+ end
+)
+
+-- Table of archive commands
+local archive_commands = {
+ ["%.zip$"] = {
+ {command = "zip", args = {"-r"}, level_arg = "-", level_min = 0, level_max = 9, passwordable = true},
+ {
+ command = {"7z", "7zz", "7za"},
+ args = {"a", "-tzip"},
+ level_arg = "-mx=",
+ level_min = 0,
+ level_max = 9,
+ passwordable = true
+ },
+ {
+ command = {"tar", "bsdtar"},
+ args = {"-caf"},
+ level_arg = {"--option", "compression-level="},
+ level_min = 1,
+ level_max = 9
+ }
+ },
+ ["%.7z$"] = {
+ {
+ command = {"7z", "7zz", "7za"},
+ args = {"a"},
+ level_arg = "-mx=",
+ level_min = 0,
+ level_max = 9,
+ header_arg = "-mhe=on",
+ passwordable = true
+ }
+ },
+ ["%.rar$"] = {
+ {
+ command = "rar",
+ args = {"a"},
+ level_arg = "-m",
+ level_min = 0,
+ level_max = 5,
+ header_arg = "-hp",
+ passwordable = true
+ }
+ },
+ ["%.tar.gz$"] = {
+ {command = {"tar", "bsdtar"}, args = {"rpf"}, level_arg = "-", level_min = 1, level_max = 9, compress = "gzip"},
+ {
+ command = {"tar", "bsdtar"},
+ args = {"rpf"},
+ level_arg = "-mx=",
+ level_min = 1,
+ level_max = 9,
+ compress = "7z",
+ compress_args = {"a", "-tgzip"}
+ },
+ {
+ command = {"tar", "bsdtar"},
+ args = {"-czf"},
+ level_arg = {"--option", "gzip:compression-level="},
+ level_min = 1,
+ level_max = 9
+ }
+ },
+ ["%.tar.xz$"] = {
+ {command = {"tar", "bsdtar"}, args = {"rpf"}, level_arg = "-", level_min = 1, level_max = 9, compress = "xz"},
+ {
+ command = {"tar", "bsdtar"},
+ args = {"rpf"},
+ level_arg = "-mx=",
+ level_min = 1,
+ level_max = 9,
+ compress = "7z",
+ compress_args = {"a", "-txz"}
+ },
+ {
+ command = {"tar", "bsdtar"},
+ args = {"-cJf"},
+ level_arg = {"--option", "xz:compression-level="},
+ level_min = 1,
+ level_max = 9
+ }
+ },
+ ["%.tar.bz2$"] = {
+ {command = {"tar", "bsdtar"}, args = {"rpf"}, level_arg = "-", level_min = 1, level_max = 9, compress = "bzip2"},
+ {
+ command = {"tar", "bsdtar"},
+ args = {"rpf"},
+ level_arg = "-mx=",
+ level_min = 1,
+ level_max = 9,
+ compress = "7z",
+ compress_args = {"a", "-tbzip2"}
+ },
+ {
+ command = {"tar", "bsdtar"},
+ args = {"-cjf"},
+ level_arg = {"--option", "bzip2:compression-level="},
+ level_min = 1,
+ level_max = 9
+ }
+ },
+ ["%.tar.zst$"] = {
+ {
+ command = {"tar", "bsdtar"},
+ args = {"rpf"},
+ level_arg = "-",
+ level_min = 1,
+ level_max = 22,
+ compress = "zstd",
+ compress_args = {"--ultra"}
+ }
+ },
+ ["%.tar.lz4$"] = {
+ {
+ command = {"tar", "bsdtar"},
+ args = {"rpf"},
+ level_arg = "-",
+ level_min = 1,
+ level_max = 12,
+ compress = "lz4"
+ }
+ },
+ ["%.tar.lha$"] = {
+ {
+ command = {"tar", "bsdtar"},
+ args = {"rpf"},
+ level_arg = "-o",
+ level_min = 5,
+ level_max = 7,
+ compress = "lha",
+ compress_args = {"-a"}
+ }
+ },
+ ["%.tar$"] = {
+ {command = {"tar", "bsdtar"}, args = {"rpf"}}
+ }
+}
+
return {
- entry = function()
- -- Exit visual mode
- ya.manager_emit("escape", { visual = true })
+ entry = function(_, job)
+ -- Parse flags and default extension
+ if job.args ~= nil then
+ for _, arg in ipairs(job.args) do
+ if arg:match("^%-(%w+)$") then
+ -- Handle combined flags (e.g., -phl)
+ for flag in arg:sub(2):gmatch(".") do
+ if flag == "p" then
+ is_password = true
+ elseif flag == "h" then
+ is_encrypted = true
+ elseif flag == "l" then
+ is_level = true
+ end
+ end
+ elseif arg:match("^%w[%w\\.]*$") then
+ -- Handle default extension (e.g., 7z, zip)
+ if archive_commands["%." .. arg .. "$"] then
+ default_extension = arg
+ else
+ notify_error(string.format("Unsupported extension: %s", arg), "warn")
+ end
+ else
+ notify_error(string.format("Unknown argument: %s", arg), "warn")
+ end
+ end
+ end
- -- Define file table and output_dir (pwd)
- local path_fnames, output_dir = selected_or_hovered()
+ -- Exit visual mode
+ ya.emit("escape", {visual = true})
+ -- Define file table and output_dir (pwd)
+ local path_fnames, fnames, output_dir = selected_or_hovered()
+ -- Get archive filename
+ local output_name, event =
+ ya.input(
+ {
+ title = "Create archive:",
+ position = {"top-center", y = 3, w = 40}
+ }
+ )
+ if event ~= 1 then
+ return
+ end
- -- Get input
- local output_name, event = ya.input({
- title = "Create archive:",
- position = { "top-center", y = 3, w = 40 },
- })
- if event ~= 1 then
- return
- end
+ -- Determine the default name for the archive
+ local default_name = #fnames == 1 and fnames[1] or Url(output_dir).name
+ output_name = output_name == "" and string.format("%s.%s", default_name, default_extension) or output_name
- -- 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" } },
- }
+ -- Add default extension if none is specified
+ if not output_name:match("%.%w+$") then
+ output_name = string.format("%s.%s", output_name, default_extension)
+ end
- 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
+ -- Validate the final archive filename
+ if not is_valid_filename(output_name) then
+ notify_error("Invalid archive filename", "error")
+ return
+ 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
+ -- Match user input to archive command
+ local archive_cmd,
+ archive_args,
+ archive_compress,
+ archive_level_arg,
+ archive_level_min,
+ archive_level_max,
+ archive_header_arg,
+ archive_passwordable,
+ archive_compress_args
+ local matched_pattern = false
+ for pattern, cmd_list in pairs(archive_commands) do
+ if output_name:match(pattern) then
+ matched_pattern = true -- Mark that file extension is correct
+ for _, cmd in ipairs(cmd_list) do
+ -- Check if archive_cmd is available
+ local find_command = type(cmd.command) == "table" and find_command_name(cmd.command) or cmd.command
+ if is_command_available(find_command) then
+ -- Check if compress_cmd (if listed) is available
+ if cmd.compress == nil or is_command_available(cmd.compress) then
+ archive_cmd = find_command
+ archive_args = cmd.args
+ archive_compress = cmd.compress or ""
+ archive_level_arg = is_level and cmd.level_arg or ""
+ archive_level_min = cmd.level_min
+ archive_level_max = cmd.level_max
+ archive_header_arg = is_encrypted and cmd.header_arg or ""
+ archive_passwordable = cmd.passwordable or false
+ archive_compress_args = cmd.compress_args or {}
+ break
+ end
+ end
+ end
+ if archive_cmd then
+ break
+ end
+ 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 extension
+ if not matched_pattern then
+ notify_error("Unsupported file extension", "error")
+ return
+ end
- -- Check if no archive command is available for the extention
- if not archive_cmd then
- notify_error("Unsupported file extention", "error")
- return
- end
+ -- Check if no suitable archive program was found
+ if not archive_cmd then
+ notify_error("Could not find a suitable archive program for the selected file extension", "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
+ -- Check if archive command has multiple names
+ if type(archive_cmd) == "table" then
+ archive_cmd = find_command_name(archive_cmd)
+ 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
+ -- 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
- -- 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
+ -- 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
- -- 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
+ -- Add password arg if selected
+ if archive_passwordable and is_password then
+ local output_password, event =
+ ya.input(
+ {
+ title = "Enter password:",
+ obscure = true,
+ position = {"top-center", y = 3, w = 40}
+ }
+ )
+ if event ~= 1 then
+ return
+ end
+ if output_password ~= "" then
+ cmd_password = "-P" .. output_password
+ if archive_cmd == "rar" and is_encrypted then
+ cmd_password = archive_header_arg .. output_password -- Add archive arg for rar
+ end
+ table.insert(archive_args, cmd_password)
+ 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,
+ -- Add header arg if selected for 7z
+ if is_encrypted and archive_header_arg ~= "" and archive_cmd ~= "rar" then
+ table.insert(archive_args, archive_header_arg)
+ end
+
+ -- Add level arg if selected
+ if archive_level_arg ~= "" and is_level then
+ local output_level, event =
+ ya.input(
+ {
+ title = string.format("Enter compression level (%s - %s)", archive_level_min, archive_level_max),
+ position = {"top-center", y = 3, w = 40}
+ }
+ )
+ if event ~= 1 then
+ return
+ end
+ -- Validate user input for compression level
+ if
+ output_level ~= "" and tonumber(output_level) ~= nil and tonumber(output_level) >= archive_level_min and
+ tonumber(output_level) <= archive_level_max
+ then
+ cmd_level =
+ type(archive_level_arg) == "table" and archive_level_arg[#archive_level_arg] .. output_level or
+ archive_level_arg .. output_level
+ local target_args = archive_compress == "" and archive_args or archive_compress_args
+ if type(archive_level_arg) == "table" then
+ -- Insert each element of archive_level_arg (except last) into target_args at the correct position
+ for i = 1, #archive_level_arg - 1 do
+ table.insert(target_args, i, archive_level_arg[i])
+ end
+ table.insert(target_args, #archive_level_arg, cmd_level) -- Add level at the end
+ else
+ -- Insert the compression level argument at the start if not a table
+ table.insert(target_args, 1, cmd_level)
+ end
+ else
+ notify_error("Invalid level specified. Using defaults.", "warn")
+ end
+ end
+
+ -- Store the original output name for later use
+ local original_name = output_name
+
+ -- If compression is needed, adjust the output name to exclude extensions like ".tar"
+ if archive_compress ~= "" then
+ output_name = output_name:match("(.*%.tar)") or output_name
+ end
+
+ -- Create a temporary directory for intermediate files
+ local temp_dir_name = ".tmp_compress"
+ local temp_dir = combine_url(output_dir, temp_dir_name)
+ local temp_dir, _ = tostring(fs.unique_name(Url(temp_dir)))
+
+ -- Attempt to create the temporary directory
+ local temp_dir_status, temp_dir_err = fs.create("dir_all", Url(temp_dir))
+ if not temp_dir_status then
+ -- Notify the user if the temporary directory creation fails
+ notify_error(string.format("Failed to create temp directory, error code: %s", temp_dir_err), "error")
+ return
+ end
+
+ -- Define the temporary output file path within the temporary directory
+ local temp_output_url = combine_url(temp_dir, output_name)
+
+ -- Add files to the output archive
+ for filepath, filenames in pairs(path_fnames) do
+ -- Execute the archive command for each path and its respective files
+ local archive_status, archive_err =
+ Command(archive_cmd):arg(archive_args):arg(temp_output_url):arg(filenames):cwd(filepath):spawn():wait()
+ if not archive_status or not archive_status.success then
+ -- Notify the user if the archiving process fails and clean up the temporary directory
+ notify_error(string.format("Failed to create archive %s with '%s', error: %s", output_name, archive_cmd, archive_err), "error")
+ local cleanup_status, cleanup_err = fs.remove("dir_all", Url(temp_dir))
+ if not cleanup_status then
+ notify_error(string.format("Failed to clean up temporary directory %s, error: %s", temp_dir, cleanup_err), "error")
+ end
+ return
+ end
+ end
+
+ -- If compression is required, execute the compression command
+ if archive_compress ~= "" then
+ local compress_status, compress_err =
+ Command(archive_compress):arg(archive_compress_args):arg(temp_output_url):spawn():wait()
+ if not compress_status or not compress_status.success then
+ -- Notify the user if the compression process fails and clean up the temporary directory
+ notify_error(string.format("Failed to compress archive %s with '%s', error: %s", output_name, archive_compress, compress_err), "error")
+ local cleanup_status, cleanup_err = fs.remove("dir_all", Url(temp_dir))
+ if not cleanup_status then
+ notify_error(string.format("Failed to clean up temporary directory %s, error: %s", temp_dir, cleanup_err), "error")
+ end
+ return
+ end
+ end
+
+ -- Move the final file from the temporary directory to the output directory
+ local final_output_url, temp_url_processed = combine_url(output_dir, original_name), combine_url(temp_dir, original_name)
+ final_output_url, _ = tostring(fs.unique_name(Url(final_output_url)))
+ local move_status, move_err = os.rename(temp_url_processed, final_output_url)
+ if not move_status then
+ -- Notify the user if the move operation fails and clean up the temporary directory
+ notify_error(string.format("Failed to move %s to %s, error: %s", temp_url_processed, final_output_url, move_err), "error")
+ local cleanup_status, cleanup_err = fs.remove("dir_all", Url(temp_dir))
+ if not cleanup_status then
+ notify_error(string.format("Failed to clean up temporary directory %s, error: %s", temp_dir, cleanup_err), "error")
+ end
+ return
+ end
+
+ -- Cleanup the temporary directory after successful operation
+ local cleanup_status, cleanup_err = fs.remove("dir_all", Url(temp_dir))
+ if not cleanup_status then
+ notify_error(string.format("Failed to clean up temporary directory %s, error: %s", temp_dir, cleanup_err), "error")
+ end
+ end
}
diff --git a/yazi/plugins/compress.yazi/main.lua.bak b/yazi/plugins/compress.yazi/main.lua.bak
deleted file mode 100755
index 333587f..0000000
--- a/yazi/plugins/compress.yazi/main.lua.bak
+++ /dev/null
@@ -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,
-}
diff --git a/yazi/plugins/git.yazi/README.md b/yazi/plugins/git.yazi/README.md
index 4c5b07c..96a87a8 100644
--- a/yazi/plugins/git.yazi/README.md
+++ b/yazi/plugins/git.yazi/README.md
@@ -1,8 +1,5 @@
# git.yazi
-> [!NOTE]
-> Yazi v25.2.26 or later is required for this plugin to work.
-
Show the status of Git file changes as linemode in the file list.
https://github.com/user-attachments/assets/34976be9-a871-4ffe-9d5a-c4cdd0bf4576
@@ -10,7 +7,7 @@ https://github.com/user-attachments/assets/34976be9-a871-4ffe-9d5a-c4cdd0bf4576
## Installation
```sh
-ya pack -a yazi-rs/plugins:git
+ya pkg add yazi-rs/plugins:git
```
## Setup
@@ -37,6 +34,9 @@ run = "git"
## Advanced
+> [!NOTE]
+> The following configuration must be put before `require("git"):setup()`
+
You can customize the [Style](https://yazi-rs.github.io/docs/plugins/layout#style) of the status sign with:
- `th.git.modified`
diff --git a/yazi/plugins/git.yazi/main.lua b/yazi/plugins/git.yazi/main.lua
index 0445ca9..e6b3a36 100644
--- a/yazi/plugins/git.yazi/main.lua
+++ b/yazi/plugins/git.yazi/main.lua
@@ -1,10 +1,11 @@
---- @since 25.4.4
+--- @since 25.5.31
local WINDOWS = ya.target_family() == "windows"
-- The code of supported git status,
-- also used to determine which status to show for directories when they contain different statuses
-- see `bubble_up`
+---@enum CODES
local CODES = {
excluded = 100, -- ignored directory
ignored = 6, -- ignored file
@@ -26,6 +27,8 @@ local PATTERNS = {
{ "[AD][AD]", CODES.updated },
}
+---@param line string
+---@return CODES, string
local function match(line)
local signs = line:sub(1, 2)
for _, p in ipairs(PATTERNS) do
@@ -41,9 +44,12 @@ local function match(line)
else
return code, path
end
+ ---@diagnostic disable-next-line: missing-return
end
end
+---@param cwd Url
+---@return string?
local function root(cwd)
local is_worktree = function(url)
local file, head = io.open(tostring(url)), nil
@@ -64,6 +70,8 @@ local function root(cwd)
until not cwd
end
+---@param changed Changes
+---@return Changes
local function bubble_up(changed)
local new, empty = {}, Url("")
for path, code in pairs(changed) do
@@ -79,6 +87,10 @@ local function bubble_up(changed)
return new
end
+---@param excluded string[]
+---@param cwd Url
+---@param repo Url
+---@return Changes
local function propagate_down(excluded, cwd, repo)
local new, rel = {}, cwd:strip_prefix(repo)
for _, path in ipairs(excluded) do
@@ -95,7 +107,12 @@ local function propagate_down(excluded, cwd, repo)
return new
end
+---@param cwd string
+---@param repo string
+---@param changed Changes
local add = ya.sync(function(st, cwd, repo, changed)
+ ---@cast st State
+
st.dirs[cwd] = repo
st.repos[repo] = st.repos[repo] or {}
for path, code in pairs(changed) do
@@ -108,16 +125,29 @@ local add = ya.sync(function(st, cwd, repo, changed)
st.repos[repo][path] = code
end
end
- ya.render()
+ -- TODO: remove this
+ if ui.render then
+ ui.render()
+ else
+ ya.render()
+ end
end)
+---@param cwd string
local remove = ya.sync(function(st, cwd)
+ ---@cast st State
+
local repo = st.dirs[cwd]
if not repo then
return
end
- ya.render()
+ -- TODO: remove this
+ if ui.render then
+ ui.render()
+ else
+ ya.render()
+ end
st.dirs[cwd] = nil
if not st.repos[repo] then
return
@@ -131,9 +161,11 @@ local remove = ya.sync(function(st, cwd)
st.repos[repo] = nil
end)
+---@param st State
+---@param opts Options
local function setup(st, opts)
- st.dirs = {} -- Mapping between a directory and its corresponding repository
- st.repos = {} -- Mapping between a repository and the status of each of its files
+ st.dirs = {}
+ st.repos = {}
opts = opts or {}
opts.order = opts.order or 1500
@@ -174,6 +206,7 @@ local function setup(st, opts)
end, opts.order)
end
+---@type UnstableFetcher
local function fetch(_, job)
local cwd = job.files[1].url.base
local repo = root(cwd)
diff --git a/yazi/plugins/git.yazi/main.lua.bak b/yazi/plugins/git.yazi/main.lua.bak
deleted file mode 100755
index a64b749..0000000
--- a/yazi/plugins/git.yazi/main.lua.bak
+++ /dev/null
@@ -1,211 +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(self, job)
- -- TODO: remove this once Yazi 0.4 is released
- job = job or self
-
- 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 code " .. tostring(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 }
diff --git a/yazi/plugins/smart-enter.yazi/README.md b/yazi/plugins/smart-enter.yazi/README.md
index d4c6bbd..742f2e1 100644
--- a/yazi/plugins/smart-enter.yazi/README.md
+++ b/yazi/plugins/smart-enter.yazi/README.md
@@ -5,7 +5,7 @@
## Installation
```sh
-ya pack -a yazi-rs/plugins:smart-enter
+ya pkg add yazi-rs/plugins:smart-enter
```
## Usage
@@ -13,7 +13,7 @@ ya pack -a yazi-rs/plugins:smart-enter
Bind your l key to the plugin, in your `~/.config/yazi/keymap.toml`:
```toml
-[[manager.prepend_keymap]]
+[[mgr.prepend_keymap]]
on = "l"
run = "plugin smart-enter"
desc = "Enter the child directory, or open the file"
@@ -36,5 +36,5 @@ require("smart-enter"):setup {
This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.
-[open]: https://yazi-rs.github.io/docs/configuration/keymap/#manager.open
-[enter]: https://yazi-rs.github.io/docs/configuration/keymap/#manager.enter
+[open]: https://yazi-rs.github.io/docs/configuration/keymap/#mgr.open
+[enter]: https://yazi-rs.github.io/docs/configuration/keymap/#mgr.enter
diff --git a/yazi/plugins/smart-enter.yazi/main.lua b/yazi/plugins/smart-enter.yazi/main.lua
index 8b9ae55..e9e2ec6 100644
--- a/yazi/plugins/smart-enter.yazi/main.lua
+++ b/yazi/plugins/smart-enter.yazi/main.lua
@@ -1,11 +1,11 @@
---- @since 25.2.26
+--- @since 25.5.31
--- @sync entry
local function setup(self, opts) self.open_multi = opts.open_multi end
local function entry(self)
local h = cx.active.current.hovered
- ya.mgr_emit(h and h.cha.is_dir and "enter" or "open", { hovered = not self.open_multi })
+ ya.emit(h and h.cha.is_dir and "enter" or "open", { hovered = not self.open_multi })
end
return { entry = entry, setup = setup }
diff --git a/yazi/plugins/smart-enter.yazi/main.lua.bak b/yazi/plugins/smart-enter.yazi/main.lua.bak
deleted file mode 100755
index 37a465a..0000000
--- a/yazi/plugins/smart-enter.yazi/main.lua.bak
+++ /dev/null
@@ -1,10 +0,0 @@
---- @sync entry
-
-local function setup(self, opts) self.open_multi = opts.open_multi end
-
-local function entry(self)
- local h = cx.active.current.hovered
- ya.manager_emit(h and h.cha.is_dir and "enter" or "open", { hovered = not self.open_multi })
-end
-
-return { entry = entry, setup = setup }
diff --git a/yazi/plugins/yamb.yazi/main.lua.bak b/yazi/plugins/yamb.yazi/main.lua.bak
deleted file mode 100755
index f3f46dc..0000000
--- a/yazi/plugins/yamb.yazi/main.lua.bak
+++ /dev/null
@@ -1,355 +0,0 @@
-local path_sep = package.config:sub(1, 1)
-
-local get_hovered_path = ya.sync(function(state)
- local h = cx.active.current.hovered
- if h then
- local path = tostring(h.url)
- if h.cha.is_dir then
- return path .. path_sep
- end
- return path
- else
- return ''
- end
-end)
-
-local get_state_attr = ya.sync(function(state, attr)
- return state[attr]
-end)
-
-local set_state_attr = ya.sync(function(state, attr, value)
- state[attr] = value
-end)
-
-local set_bookmarks = ya.sync(function(state, path, value)
- state.bookmarks[path] = value
-end)
-
-local sort_bookmarks = function(bookmarks, key1, key2, reverse)
- reverse = reverse or false
- table.sort(bookmarks, function(x, y)
- if x[key1] == nil and y[key1] == nil then
- return x[key2] < y[key2]
- elseif x[key1] == nil then
- return false
- elseif y[key1] == nil then
- return true
- else
- return x[key1] < y[key1]
- end
- end)
- if reverse then
- local n = #bookmarks
- for i = 1, math.floor(n / 2) do
- bookmarks[i], bookmarks[n - i + 1] = bookmarks[n - i + 1], bookmarks[i]
- end
- end
- return bookmarks
-end
-
-local save_to_file = function(mb_path, bookmarks)
- local file = io.open(mb_path, "w")
- if file == nil then
- return
- end
- local array = {}
- for _, item in pairs(bookmarks) do
- table.insert(array, item)
- end
- sort_bookmarks(array, "tag", "key", true)
- for _, item in ipairs(array) do
- file:write(string.format("%s\t%s\t%s\n", item.tag, item.path, item.key))
- end
- file:close()
-end
-
-local fzf_find = function(cli, mb_path)
- local permit = ya.hide()
- local cmd = string.format("%s < \"%s\"", cli, mb_path)
- local handle = io.popen(cmd, "r")
- local result = ""
- if handle then
- -- strip
- result = string.gsub(handle:read("*all") or "", "^%s*(.-)%s*$", "%1")
- handle:close()
- end
- permit:drop()
- local tag, path, key = string.match(result or "", "(.-)\t(.-)\t(.*)")
- return path
-end
-
-local which_find = function(bookmarks)
- local cands = {}
- for path, item in pairs(bookmarks) do
- if #item.tag ~= 0 then
- table.insert(cands, { desc = item.tag, on = item.key, path = item.path })
- end
- end
- sort_bookmarks(cands, "on", "desc", false)
- if #cands == 0 then
- ya.notify {
- title = "Bookmarks",
- content = "Empty bookmarks",
- timeout = 2,
- level = "info",
- }
- return nil
- end
- local idx = ya.which { cands = cands }
- if idx == nil then
- return nil
- end
- return cands[idx].path
-end
-
-local action_jump = function(bookmarks, path, jump_notify)
- if path == nil then
- return
- end
- local tag = bookmarks[path].tag
- if string.sub(path, -1) == path_sep then
- ya.manager_emit("cd", { path })
- else
- ya.manager_emit("reveal", { path })
- end
- if jump_notify then
- ya.notify {
- title = "Bookmarks",
- content = 'Jump to "' .. tag .. '"',
- timeout = 2,
- level = "info",
- }
- end
-end
-
-local generate_key = function(bookmarks)
- local keys = get_state_attr("keys")
- local key2rank = get_state_attr("key2rank")
- local mb = {}
- for _, item in pairs(bookmarks) do
- if #item.key == 1 then
- table.insert(mb, item.key)
- end
- end
- if #mb == 0 then
- return keys[1]
- end
- table.sort(mb, function(a, b)
- return key2rank[a] < key2rank[b]
- end)
- local idx = 1
- for _, key in ipairs(keys) do
- if key2rank[key] < key2rank[mb[idx]] then
- return key
- end
- idx = idx + 1
- end
- return nil
-end
-
-local action_save = function(mb_path, bookmarks, path)
- if path == nil or #path == 0 then
- return
- end
-
- local path_obj = bookmarks[path]
- -- check tag
- local tag = path_obj and path_obj.tag or path:match(".*[\\/]([^\\/]+)[\\/]?$")
- while true do
- local value, event = ya.input({
- title = "Tag (alias name)",
- value = tag,
- position = { "top-center", y = 3, w = 40 },
- })
- if event ~= 1 then
- return
- end
- tag = value or ''
- if #tag == 0 then
- ya.notify {
- title = "Bookmarks",
- content = "Empty tag",
- timeout = 2,
- level = "info",
- }
- else
- -- check the tag
- local tag_obj = nil
- for _, item in pairs(bookmarks) do
- if item.tag == tag then
- tag_obj = item
- break
- end
- end
- if tag_obj == nil or tag_obj.path == path then
- break
- end
- ya.notify {
- title = "Bookmarks",
- content = "Duplicated tag",
- timeout = 2,
- level = "info",
- }
- end
- end
- -- check key
- local key = path_obj and path_obj.key or generate_key(bookmarks)
- while true do
- local value, event = ya.input({
- title = "Key (1 character, optional)",
- value = key,
- position = { "top-center", y = 3, w = 40 },
- })
- if event ~= 1 then
- return
- end
- key = value or ""
- if key == "" then
- key = ""
- break
- elseif #key == 1 then
- -- check the key
- local key_obj = nil
- for _, item in pairs(bookmarks) do
- if item.key == key then
- key_obj = item
- break
- end
- end
- if key_obj == nil or key_obj.path == path then
- break
- else
- ya.notify {
- title = "Bookmarks",
- content = "Duplicated key",
- timeout = 2,
- level = "info",
- }
- end
- else
- ya.notify {
- title = "Bookmarks",
- content = "The length of key shoule be 1",
- timeout = 2,
- level = "info",
- }
- end
- end
- -- save
- set_bookmarks(path, { tag = tag, path = path, key = key })
- bookmarks = get_state_attr("bookmarks")
- save_to_file(mb_path, bookmarks)
- ya.notify {
- title = "Bookmarks",
- content = '"' .. tag .. '" saved"',
- timeout = 2,
- level = "info",
- }
-end
-
-local action_delete = function(mb_path, bookmarks, path)
- if path == nil then
- return
- end
- local tag = bookmarks[path].tag
- set_bookmarks(path, nil)
- bookmarks = get_state_attr("bookmarks")
- save_to_file(mb_path, bookmarks)
- ya.notify {
- title = "Bookmarks",
- content = '"' .. tag .. '" deleted',
- timeout = 2,
- level = "info",
- }
-end
-
-local action_delete_all = function(mb_path)
- local value, event = ya.input({
- title = "Delete all bookmarks? (y/n)",
- position = { "top-center", y = 3, w = 40 },
- })
- if event ~= 1 then
- return
- end
- if string.lower(value) == "y" then
- set_state_attr("bookmarks", {})
- save_to_file(mb_path, {})
- ya.notify {
- title = "Bookmarks",
- content = "All bookmarks deleted",
- timeout = 2,
- level = "info",
- }
- else
- ya.notify {
- title = "Bookmarks",
- content = "Cancel delete",
- timeout = 2,
- level = "info",
- }
- end
-end
-
-return {
- setup = function(state, options)
- state.path = options.path or
- (ya.target_family() == "windows" and os.getenv("APPDATA") .. "\\yazi\\config\\bookmark") or
- (os.getenv("HOME") .. "/.config/yazi/bookmark")
- state.cli = options.cli or "fzf"
- state.jump_notify = options.jump_notify and true
- -- init the keys
- local keys = options.keys or "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
- state.keys = {}
- state.key2rank = {}
- for i = 1, #keys do
- local char = keys:sub(i, i)
- table.insert(state.keys, char)
- state.key2rank[char] = i
- end
-
- -- init the bookmarks
- local bookmarks = {}
- for _, item in pairs(options.bookmarks or {}) do
- bookmarks[item.path] = { tag = item.tag, path = item.path, key = item.key }
- end
- -- load the config
- local file = io.open(state.path, "r")
- if file ~= nil then
- for line in file:lines() do
- local tag, path, key = string.match(line, "(.-)\t(.-)\t(.*)")
- if tag and path then
- key = key or ""
- bookmarks[path] = { tag = tag, path = path, key = key }
- end
- end
- file:close()
- end
- -- create bookmarks file to enable fzf
- save_to_file(state.path, bookmarks)
- state.bookmarks = bookmarks
- end,
- entry = function(self, jobs)
- local action = jobs.args[1]
- if not action then
- return
- end
- local mb_path, cli, bookmarks, jump_notify = get_state_attr("path"), get_state_attr("cli"), get_state_attr("bookmarks"), get_state_attr("jump_notify")
- if action == "save" then
- action_save(mb_path, bookmarks, get_hovered_path())
- elseif action == "delete_by_key" then
- action_delete(mb_path, bookmarks, which_find(bookmarks))
- elseif action == "delete_by_fzf" then
- action_delete(mb_path, bookmarks, fzf_find(cli, mb_path))
- elseif action == "delete_all" then
- action_delete_all(mb_path)
- elseif action == "jump_by_key" then
- action_jump(bookmarks, which_find(bookmarks), jump_notify)
- elseif action == "jump_by_fzf" then
- action_jump(bookmarks, fzf_find(cli, mb_path), jump_notify)
- elseif action == "rename_by_key" then
- action_save(mb_path, bookmarks, which_find(bookmarks))
- elseif action == "rename_by_fzf" then
- action_save(mb_path, bookmarks, fzf_find(cli, mb_path))
- end
- end,
-}
diff --git a/yazi/plugins/yaziline.yazi/README.md b/yazi/plugins/yaziline.yazi/README.md
index fedd392..eeb8d12 100644
--- a/yazi/plugins/yaziline.yazi/README.md
+++ b/yazi/plugins/yaziline.yazi/README.md
@@ -4,23 +4,20 @@ Simple lualine-like status line for yazi.
Read more about features and configuration [here](#features).
-> ⚠️ **Note**:
-> If you experience any issues after updating, please refer to the latest release notes. This repository is continuously synced with the upstream Yazi source code, which is actively maintained and frequently updated.
-

## Requirements
-- yazi version >= [25.4.8](https://github.com/sxyazi/yazi/releases/tag/v25.4.8)
+- yazi version >= [25.5.28](https://github.com/sxyazi/yazi/releases/tag/v25.5.28)
- Font with symbol support. For example [Nerd Fonts](https://www.nerdfonts.com/).
## Installation
```sh
-ya pack -a llanosrocas/yaziline
+ya pkg add llanosrocas/yaziline
```
-Or manually copy `init.lua` to the `~/.config/yazi/plugins/yaziline.yazi/init.lua`
+Or manually copy `main.lua` to the `~/.config/yazi/plugins/yaziline.yazi/main.lua`
## Usage
@@ -35,6 +32,7 @@ Optionally, configure line:
```lua
require("yaziline"):setup({
color = "#98c379", -- main theme color
+ secondary_color = "#5A6078", -- secondary color
default_files_color = "darkgray", -- color of the file counter when it's inactive
selected_files_color = "white",
yanked_files_color = "green",
@@ -57,6 +55,20 @@ require("yaziline"):setup({
})
```
+```
+ MODE size long_file...name.md S 0 Y 0
+| | | | | | | | |
+| | | | | | | | └─── yank_symbol
+| | | | | | | └─────── select_symbol
+| | | | | | └───────── separator_close_thin
+| | | | | └─────────────────── filename_truncate_separator
+| | | | └─────────────────────────────── separator_close
+| | | └────────────────────────────────── secondary_color
+| | └────────────────────────────────────── separator_close
+| └────────────────────────────────────────── color
+└───────────────────────────────────────────── separator_head
+```
+
## Features
### Preconfigured separators
diff --git a/yazi/plugins/yaziline.yazi/main.lua b/yazi/plugins/yaziline.yazi/main.lua
index 2f7daac..3906079 100644
--- a/yazi/plugins/yaziline.yazi/main.lua
+++ b/yazi/plugins/yaziline.yazi/main.lua
@@ -1,3 +1,5 @@
+---@diagnostic disable: undefined-global
+
local function setup(_, options)
options = options or {}
@@ -26,6 +28,7 @@ local function setup(_, options)
filename_truncate_separator = options.filename_truncate_separator or "...",
color = options.color or nil,
+ secondary_color = options.secondary_color or nil,
default_files_color = options.default_files_color
or th.which.separator_style.fg
or "darkgray",
@@ -61,12 +64,12 @@ local function setup(_, options)
function Status:size()
local h = self._current.hovered
- local size = h and ya.readable_size(h:size() or h.cha.len)
+ local size = h and (h:size() or h.cha.len) or 0
local style = self:style()
- return ui.Span(current_separator_style.separator_close .. " " .. size .. " ")
+ return ui.Span(current_separator_style.separator_close .. " " .. ya.readable_size(size) .. " ")
:fg(config.color or style.main.bg)
- :bg(th.which.separator_style.fg)
+ :bg(config.secondary_color or th.which.separator_style.fg)
end
function Status:utf8_sub(str, start_char, end_char)
@@ -95,21 +98,26 @@ local function setup(_, options)
end
function Status:name()
- local h = self._current.hovered
- if not h then
- return ""
- end
+ local h = self._current.hovered
+ if not h then
+ return ui.Line({
+ ui.Span(current_separator_style.separator_close .. " ")
+ :fg(config.secondary_color or th.which.separator_style.fg),
+ ui.Span("Empty dir")
+ :fg(config.color or style.main.bg),
+ })
+ end
- local truncated_name = self:truncate_name(h.name, config.filename_max_length)
+ local truncated_name = self:truncate_name(h.name, config.filename_max_length)
- local style = self:style()
- return ui.Line {
- ui.Span(current_separator_style.separator_close .. " ")
- :fg(th.which.separator_style.fg),
- ui.Span(truncated_name)
+ local style = self:style()
+ return ui.Line({
+ ui.Span(current_separator_style.separator_close .. " ")
+ :fg(config.secondary_color or th.which.separator_style.fg),
+ ui.Span(truncated_name)
:fg(config.color or style.main.bg),
- }
- end
+ })
+ end
function Status:files()
local files_yanked = #cx.yanked
@@ -134,7 +142,7 @@ local function setup(_, options)
return ui.Line({
ui.Span(" " .. current_separator_style.separator_close_thin .. " ")
:fg(th.which.separator_style.fg),
- ui.Span(config.select_symbol .. " " .. files_selected .. " ")
+ ui.Span(config.select_symbol .. " " .. files_selected .. " ")
:fg(selected_fg),
ui.Span(yanked_text .. " ")
:fg(yanked_fg),
@@ -143,6 +151,11 @@ local function setup(_, options)
function Status:modified()
local hovered = cx.active.current.hovered
+
+ if not hovered then
+ return ""
+ end
+
local cha = hovered.cha
local time = (cha.mtime or 0) // 1
@@ -168,14 +181,14 @@ local function setup(_, options)
local style = self:style()
return ui.Line({
- ui.Span(" " .. current_separator_style.separator_open)
- :fg(th.which.separator_style.fg),
+ ui.Span(" " .. current_separator_style.separator_open)
+ :fg(config.secondary_color or th.which.separator_style.fg),
ui.Span(percent)
:fg(config.color or style.main.bg)
- :bg(th.which.separator_style.fg),
+ :bg(config.secondary_color or th.which.separator_style.fg),
ui.Span(current_separator_style.separator_open)
:fg(config.color or style.main.bg)
- :bg(th.which.separator_style.fg),
+ :bg(config.secondary_color or th.which.separator_style.fg),
})
end