diff --git a/.dotter/global.toml b/.dotter/global.toml index 7b905a93..307169ad 100644 --- a/.dotter/global.toml +++ b/.dotter/global.toml @@ -122,7 +122,7 @@ depends = [ "misc", "local", "eww", "lock" ] "config/spotify-tui.yml" = "~/.config/spotify-tui/config.yml" [terminal] -depends = [ "zsh", "tmux", "yazi" ] +depends = [ "zsh", "tmux" ] [terminal.files] "config/alacritty/" = "~/.config/alacritty/" diff --git a/config/HybridBar/scripts/change-active-workspace b/config/HybridBar/scripts/change-active-workspace old mode 100644 new mode 100755 diff --git a/config/HybridBar/scripts/get-active-workspace b/config/HybridBar/scripts/get-active-workspace old mode 100644 new mode 100755 diff --git a/config/HybridBar/scripts/get-window-title b/config/HybridBar/scripts/get-window-title old mode 100644 new mode 100755 diff --git a/config/HybridBar/scripts/get-workspaces b/config/HybridBar/scripts/get-workspaces old mode 100644 new mode 100755 diff --git a/config/eww/scripts/change-active-workspace b/config/eww/scripts/change-active-workspace old mode 100644 new mode 100755 diff --git a/config/eww/scripts/get-active-workspace b/config/eww/scripts/get-active-workspace old mode 100644 new mode 100755 diff --git a/config/eww/scripts/get-music b/config/eww/scripts/get-music old mode 100644 new mode 100755 diff --git a/config/eww/scripts/get-network b/config/eww/scripts/get-network old mode 100644 new mode 100755 diff --git a/config/eww/scripts/get-window-title b/config/eww/scripts/get-window-title old mode 100644 new mode 100755 diff --git a/config/eww/scripts/get-workspaces b/config/eww/scripts/get-workspaces old mode 100644 new mode 100755 diff --git a/config/eww/scripts/getvol b/config/eww/scripts/getvol old mode 100644 new mode 100755 diff --git a/config/eww/scripts/github b/config/eww/scripts/github old mode 100644 new mode 100755 diff --git a/config/lf/cleaner b/config/lf/cleaner old mode 100644 new mode 100755 diff --git a/config/lf/lfrc b/config/lf/lfrc old mode 100644 new mode 100755 diff --git a/config/nsxiv/exec/image-info b/config/nsxiv/exec/image-info old mode 100644 new mode 100755 diff --git a/config/nsxiv/exec/key-handler b/config/nsxiv/exec/key-handler old mode 100644 new mode 100755 diff --git a/config/nsxiv/exec/nsxiv-url b/config/nsxiv/exec/nsxiv-url old mode 100644 new mode 100755 diff --git a/config/nsxiv/exec/thumb-info b/config/nsxiv/exec/thumb-info old mode 100644 new mode 100755 diff --git a/config/nsxiv/exec/win-title b/config/nsxiv/exec/win-title old mode 100644 new mode 100755 diff --git a/config/shell/env b/config/shell/env index 1f7757fb..b3085a13 100644 --- a/config/shell/env +++ b/config/shell/env @@ -114,4 +114,3 @@ export _JAVA_AWT_WM_NONREPARENTING=1 # Fix for Java applications in dwm . "$XDG_DATA_HOME/cargo/env" . "$XDG_DATA_HOME/rye/env" . "$XDG_CACHE_HOME/deno/.deno/env" - diff --git a/config/x11/opt-apps b/config/x11/opt-apps old mode 100644 new mode 100755 diff --git a/config/yazi/package.toml b/config/yazi/package.toml index aceec22d..bc0f9dac 100644 --- a/config/yazi/package.toml +++ b/config/yazi/package.toml @@ -1,22 +1,82 @@ -[plugin] -deps = [ - {use = "AnirudhG07/nbpreview", rev = "1d85745" }, - {use = "Reledia/glow", rev = "5ce76dc" }, - {use = "Reledia/hexyl", rev = "39d3d4e" }, - {use = "Reledia/miller", rev = "40e0265" }, - {use = "Sonico98/exifaudio", rev = "d794614" }, - {use = "dedukun/relative-motions", rev = "df97039" }, - {use = "hankertrix/augment-command", rev = "6a367db" }, - {use = "imsi32/yatline", rev = "600ed1f" }, - {use = "kirasok/torrent-preview", rev = "c9e67df" }, - {use = "ndtoan96/ouch", rev = "b869886" }, - {use = "pirafrank/what-size", rev = "b23e3a4" }, - {use = "yazi-rs/plugins:chmod", rev = "71c4fc2" }, - {use = "yazi-rs/plugins:full-border", rev = "71c4fc2" }, - {use = "yazi-rs/plugins:git", rev = "71c4fc2" }, - {use = "yazi-rs/plugins:hide-preview", rev = "71c4fc2" }, - {use = "yazi-rs/plugins:max-preview", rev = "71c4fc2" }, -] +[[plugin.deps]] +use = "AnirudhG07/nbpreview" +rev = "1d85745" +hash = "d378328e5d0a1b9fb9f04ab3aade4575" + +[[plugin.deps]] +use = "Reledia/glow" +rev = "5ce76dc" +hash = "52e5f5c602962e7cbf874da28f52ba45" + +[[plugin.deps]] +use = "Reledia/hexyl" +rev = "39d3d4e" +hash = "dd624cbaff94af65f39fd86bc57b340" + +[[plugin.deps]] +use = "Reledia/miller" +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] -deps = [ ] +deps = [] diff --git a/config/yazi/plugins/archive.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/archive.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/archive.yazi/LICENSE b/config/yazi/plugins/archive.yazi/LICENSE deleted file mode 100644 index 7ce7a2f1..00000000 --- a/config/yazi/plugins/archive.yazi/LICENSE +++ /dev/null @@ -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. diff --git a/config/yazi/plugins/archive.yazi/README.md b/config/yazi/plugins/archive.yazi/README.md deleted file mode 100644 index 385fe38b..00000000 --- a/config/yazi/plugins/archive.yazi/README.md +++ /dev/null @@ -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. diff --git a/config/yazi/plugins/archive.yazi/init.lua b/config/yazi/plugins/archive.yazi/init.lua deleted file mode 100644 index 333587fe..00000000 --- a/config/yazi/plugins/archive.yazi/init.lua +++ /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/config/yazi/plugins/augment-command.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/augment-command.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/augment-command.yazi/init.lua b/config/yazi/plugins/augment-command.yazi/init.lua deleted file mode 100644 index c32a7051..00000000 --- a/config/yazi/plugins/augment-command.yazi/init.lua +++ /dev/null @@ -1,3107 +0,0 @@ --- Plugin to make some Yazi commands smarter --- Written in Lua 5.4 - --- Type aliases - --- The type for the arguments ----@alias Arguments table - --- The type for the function to handle a command --- --- Description of the function parameters: --- args: The arguments to pass to the command --- config: The configuration object --- command_table: The command table containing all the command functions ----@alias CommandFunction fun( ---- args: Arguments, ---- config: Configuration, ---- command_table: CommandTable): nil - --- The type of the command table ----@alias CommandTable table - --- The type for the extractor command ----@alias ExtractorFunction fun( ---- password: string|nil, ---- configuration: Configuration): CommandOutput|nil, integer - --- Custom types - --- The type of the user configuration table --- The user configuration for the plugin ----@class (exact) UserConfiguration ----@field prompt boolean Whether or not to prompt the user ----@field default_item_group_for_prompt ItemGroup The default prompt item group ----@field smart_enter boolean Whether to use smart enter ----@field smart_paste boolean Whether to use smart paste ----@field smart_tab_create boolean Whether to use smart tab create ----@field smart_tab_switch boolean Whether to use smart tab switch ----@field open_file_after_creation boolean Whether to open after creation ----@field enter_directory_after_creation boolean Whether to enter after creation ----@field use_default_create_behaviour boolean Use Yazi's create behaviour? ----@field enter_archives boolean Whether to enter archives ----@field extract_retries number How many times to retry extracting ----@field recursively_extract_archives boolean Extract inner archives or not ----@field must_have_hovered_item boolean Whether to stop when no item is hovered ----@field skip_single_subdirectory_on_enter boolean Skip single subdir on enter ----@field skip_single_subdirectory_on_leave boolean Skip single subdir on leave ----@field wraparound_file_navigation boolean Have wraparound navigation or not - --- The additional data passed to the function to initialise the configuration ----@class (exact) AdditionalData ----@field extractor_command string The extractor shell command, like 7z - --- The full configuration for the plugin ----@class (exact) Configuration: UserConfiguration, AdditionalData - --- The type for the state ----@class (exact) State ----@field config Configuration - --- The type for the extraction results ----@class (exact) ExtractionResult ----@field archive_path string ----@field successful boolean ----@field extracted_items_path string|nil ----@field error_message string|nil - --- The enum for which group of items to operate on ----@enum ItemGroup -local ItemGroup = { - Hovered = "hovered", - Selected = "selected", - None = "none", - Prompt = "prompt", -} - --- The enum for the supported commands ----@enum SupportedCommands -local Commands = { - Open = "open", - Enter = "enter", - Leave = "leave", - Rename = "rename", - Remove = "remove", - Create = "create", - Shell = "shell", - Paste = "paste", - TabCreate = "tab_create", - TabSwitch = "tab_switch", - Arrow = "arrow", - ParentArrow = "parent_arrow", - Editor = "editor", - Pager = "pager", -} - --- The extract behaviour flags --- https://documentation.help/7-Zip/overwrite.htm ----@enum ExtractBehaviour -local ExtractBehaviour = { - Overwrite = "-aoa", - Skip = "-aos", - Rename = "-aou", - RenameExisting = "-aot", -} - --- The default configuration for the plugin ----@type UserConfiguration -local DEFAULT_CONFIG = { - prompt = false, - default_item_group_for_prompt = ItemGroup.Hovered, - smart_enter = true, - smart_paste = false, - smart_tab_create = false, - smart_tab_switch = false, - open_file_after_creation = false, - enter_directory_after_creation = false, - use_default_create_behaviour = false, - enter_archives = true, - extract_retries = 3, - recursively_extract_archives = true, - must_have_hovered_item = true, - skip_single_subdirectory_on_enter = true, - skip_single_subdirectory_on_leave = true, - wraparound_file_navigation = false, -} - --- The default input options for this plugin -local DEFAULT_INPUT_OPTIONS = { - position = { "top-center", x = 0, y = 2, w = 50, h = 3 }, -} - --- The default confirm options for this plugin -local DEFAULT_CONFIRM_OPTIONS = { - pos = { "center", x = 0, y = 0, w = 50, h = 15 }, -} - --- The default notification options for this plugin -local DEFAULT_NOTIFICATION_OPTIONS = { - title = "Augment Command Plugin", - timeout = 5, -} - --- The tab preference keys. --- The values are just dummy values --- so that I don't have to maintain two --- different types for the same thing. ----@type tab.Preference -local TAB_PREFERENCE_KEYS = { - sort_by = "alphabetical", - sort_sensitive = false, - sort_reverse = false, - sort_dir_first = true, - sort_translit = false, - linemode = "none", - show_hidden = false, -} - --- The table of input options for the prompt ----@enum InputOptionsTable -local INPUT_OPTIONS_TABLE = { - [ItemGroup.Hovered] = "(H/s)", - [ItemGroup.Selected] = "(h/S)", - [ItemGroup.None] = "(h/s)", -} - --- The table of archive mime types ----@type table -local ARCHIVE_MIME_TYPES = { - ["application/zip"] = true, - ["application/gzip"] = true, - ["application/tar"] = true, - ["application/bzip"] = true, - ["application/bzip2"] = true, - ["application/7z-compressed"] = true, - ["application/rar"] = true, - ["application/xz"] = true, - - -- Bug in file(1) that classifies - -- some zip archives as a data stream, - -- hopefully this can be removed in the future. - -- - -- Link to bug report: - -- https://bugs.astron.com/view.php?id=571 - ["application/octet-stream"] = true, -} - --- The list of archive file extensions ----@type table -local ARCHIVE_FILE_EXTENSIONS = { - ["7z"] = true, - boz = true, - bz = true, - bz2 = true, - bzip2 = true, - cb7 = true, - cbr = true, - cbt = true, - cbz = true, - gz = true, - gzip = true, - rar = true, - s7z = true, - tar = true, - tbz = true, - tbz2 = true, - tgz = true, - txz = true, - xz = true, - zip = true, -} - --- The list of mime type prefixes to remove --- --- The prefixes are used in a lua pattern --- to match on the mime type, so special --- characters need to be escaped ----@type string[] -local MIME_TYPE_PREFIXES_TO_REMOVE = { - "x%-", - "vnd%.", -} - --- The pattern template to get the mime type without a prefix ----@type string -local get_mime_type_without_prefix_template_pattern = - "^(%%a-)/%s([%%-%%d%%a]-)$" - --- The pattern to get the information from an archive item ----@type string -local archive_item_info_pattern = "%s+([%.%a]+)%s+(%d+)%s+(%d+)%s+(.+)$" - --- The pattern to get the file extension ----@type string -local file_extension_pattern = "%.([%a]+)$" - --- The pattern to get the shell variables in a command ----@type string -local shell_variable_pattern = "[%$%%][%*@0]" - --- The pattern to match the bat command with the pager option passed ----@type string -local bat_command_with_pager_pattern = "%f[%a]bat%f[%A].*%-%-pager%s+" - --- Function to merge tables. --- --- The key-value pairs of the tables given later --- in the argument list WILL OVERRIDE --- the tables given earlier in the argument list. --- --- The list items in the table will be added in order, --- with the items in the first table being added first, --- and the items in the second table being added second, --- and so on. ----@param ... table[] The tables to merge ----@return table merged_table The merged table -local function merge_tables(...) - -- - - -- Initialise a new table - local new_table = {} - - -- Initialise the index variable - local index = 1 - - -- Iterates over the tables given - for _, table in ipairs({ ... }) do - -- - - -- Iterate over all of the keys and values - for key, value in pairs(table) do - -- - - -- If the key is a number, then add using the index - -- instead of the key. - -- This is to allow lists to be merged. - if type(key) == "number" then - -- - - -- Set the value mapped to the index - new_table[index] = value - - -- Increment the index - index = index + 1 - - -- Otherwise, the key isn't a number - else - -- - - -- Set the key in the new table to the value given - new_table[key] = value - end - end - end - - -- Return the new table - return new_table -end - --- Function to split a string into a list ----@param given_string string The string to split ----@param separator string The character to split the string by ----@return string[] splitted_strings The list of strings split by the character -local function string_split(given_string, separator) - -- - - -- If the separator isn't given, set it to the whitespace character - if separator == nil then separator = "%s" end - - -- Initialise the list of splitted strings - local splitted_strings = {} - - -- Iterate over all of the strings found by pattern - for string in string.gmatch(given_string, "([^" .. separator .. "]+)") do - -- - - -- Add the string to the list of splitted strings - table.insert(splitted_strings, string) - end - - -- Return the list of splitted strings - return splitted_strings -end - --- Function to trim a string ----@param string string The string to trim ----@return string trimmed_string The trimmed string -local function string_trim(string) - -- - - -- Return the string with the whitespace characters - -- removed from the start and end - return string:match("^%s*(.-)%s*$") -end - --- Function to get a value from a table --- and return the default value if the key doesn't exist ----@param table table The table to get the value from ----@param key string|number The key to get the value from ----@param default any The default value to return if the key doesn't exist -local function table_get(table, key, default) return table[key] or default end - --- Function to pop a key from a table ----@param table table The table to pop from ----@param key string|number The key to pop ----@param default any The default value to return if the key doesn't exist ----@return any value The value of the key or the default value -local function table_pop(table, key, default) - -- - - -- Get the value of the key from the table - local value = table[key] - - -- Remove the key from the table - table[key] = nil - - -- Return the value if it exist, - -- otherwise return the default value - return value or default -end - --- Function to escape a percentage sign % --- in the string that is being replaced ----@param replacement_string string The string to escape ----@return string replacement_result The escaped string -local function escape_replacement_string(replacement_string) - -- - - -- Get the result of the replacement - local replacement_result = replacement_string:gsub("%%", "%%%%") - - -- Return the result of the replacement - return replacement_result -end - --- Function to parse the number arguments to the number type ----@param args Arguments The arguments to parse ----@return Arguments parsed_args The parsed arguments -local function parse_number_arguments(args) - -- - - -- The parsed arguments - ---@type Arguments - local parsed_args = {} - - -- Iterate over the arguments given - for arg_name, arg_value in pairs(args) do - -- - - -- Try to convert the argument to a number - local number_arg_value = tonumber(arg_value) - - -- Set the argument to the number argument value - -- if the argument is a number, - -- otherwise just set it to the given argument value - parsed_args[arg_name] = number_arg_value or arg_value - end - - -- Return the parsed arguments - return parsed_args -end - --- Function to show a warning ----@param warning_message string The warning message ----@return nil -local function show_warning(warning_message) - return ya.notify(merge_tables(DEFAULT_NOTIFICATION_OPTIONS, { - content = warning_message, - level = "warn", - })) -end - --- Function to show an error ----@param error_message string The error message ----@return nil -local function show_error(error_message) - return ya.notify(merge_tables(DEFAULT_NOTIFICATION_OPTIONS, { - content = error_message, - level = "error", - })) -end - --- Function to get the user's input ----@param prompt string The prompt to show to the user ----@return string|nil user_input The user's input ----@return InputEvent event The event for the input function -local function get_user_input(prompt) - return ya.input(merge_tables(DEFAULT_INPUT_OPTIONS, { - title = prompt, - })) -end - --- Function to get the user's confirmation --- TODO: Remove the `ya.input` version once `ya.confirm` is stable ----@param prompt string The prompt to show to the user ----@param title string|ui.Line The title of the confirmation prompt ----@param content string|ui.Text The content of the confirmation prompt ----@return boolean confirmation Whether the user has confirmed or not -local function get_user_confirmation(prompt, title, content) - -- - - -- If the ya.confirm API exists, use it - if ya.confirm then - -- - - -- Get the user's confirmation - local confirmation = ya.confirm(merge_tables(DEFAULT_CONFIRM_OPTIONS, { - title = title, - content = content, - })) - - -- Return the result of the confirmation - return confirmation - end - - -- TODO: Remove everything after this when `ya.confirm` is stable - - -- Get the user's input - local user_input, event = get_user_input(prompt) - - -- If the user has not confirmed the input, - -- or the user input is nil, - -- then return false - if not user_input or event ~= 1 then return false end - - -- Lowercase the user's input - user_input = user_input:lower() - - -- If the user input starts with a "y", then return true - if user_input:find("^y") then return true end - - -- Otherwise, return false - return false -end - --- Function to merge the given configuration table with the default one ----@param config UserConfiguration|nil The configuration table to merge ----@return UserConfiguration merged_config The merged configuration table -local function merge_configuration(config) - -- - - -- If the configuration isn't given, then use the default one - if config == nil then return DEFAULT_CONFIG end - - -- Initialise the list of invalid configuration options - local invalid_configuration_options = {} - - -- Initialise the merged configuration - local merged_config = {} - - -- Iterate over the default configuration table - for key, value in pairs(DEFAULT_CONFIG) do - -- - - -- Add the default configuration to the merged configuration - merged_config[key] = value - end - - -- Iterate over the given configuration table - for key, value in pairs(config) do - -- - - -- If the key is not in the merged configuration - if merged_config[key] == nil then - -- - - -- Add the key to the list of invalid configuration options - table.insert(invalid_configuration_options, key) - - -- Continue the loop - goto continue - end - - -- Otherwise, overwrite the value in the merged configuration - merged_config[key] = value - - -- The label to continue the loop - ::continue:: - end - - -- If there are no invalid configuration options, - -- then return the merged configuration - if #invalid_configuration_options <= 0 then return merged_config end - - -- Otherwise, warn the user of the invalid configuration options - show_warning( - "Invalid configuration options: " - .. table.concat(invalid_configuration_options, ", ") - ) - - -- Return the merged configuration - return merged_config -end - --- Function to initialise the configuration ----@param state State The state object ----@param user_config Configuration|nil The configuration object ----@param additional_data AdditionalData The additional data ----@return Configuration config The initialised configuration object -local initialise_config = ya.sync(function(state, user_config, additional_data) - -- - - -- Merge the default configuration with the user given one, - -- as well as the additional data given, - -- and set it to the state. - state.config = - merge_tables(merge_configuration(user_config), additional_data) - - -- Return the configuration object for async functions - return state.config -end) - --- Function to try if a shell command exists ----@param shell_command string The shell command to check ----@return boolean shell_command_exists Whether the shell command exists -local function shell_command_exists(shell_command) - -- - - -- Initialise the null output - local null_output = "/dev/null" - - -- If the OS is Windows - if ya.target_family() == "windows" then - -- - - -- Set the null output to the NUL device - null_output = "NUL" - end - - -- Get whether the shell command is successfully executed - -- - -- "1> /dev/null" redirects the standard output - -- of the shell command to /dev/null, which accepts - -- and discards all input and produces no output. - -- - -- "2>&1" redirects the standard error to the file - -- descriptor of the standard output, which is the - -- /dev/null file or the NUL device on Windows, - -- which accepts and discards - -- all input and produces no output. - -- - -- The full thing, "1> /dev/null 2>&1" just makes sure - -- the shell command doesn't produce any output when executed. - -- - -- The equivalent command on Windows is "1> NUL 2>&1". - -- - -- https://stackoverflow.com/questions/10508843/what-is-dev-null-21 - -- https://stackoverflow.com/questions/818255/what-does-21-mean - -- https://www.gnu.org/software/bash/manual/html_node/Redirections.html - local successfully_executed = - os.execute(shell_command .. " 1> " .. null_output .. " 2>&1") - - -- If the command was not successfully executed, - -- set the successfully executed variable to false - if not successfully_executed then successfully_executed = false end - - -- Return the result of the os.execute command - return successfully_executed -end - --- Function to initialise the plugin ----@param opts Configuration|nil The options given to the plugin ----@return Configuration config The initialised configuration object -local function initialise_plugin(opts) - -- - - -- Initialise the extractor command - local extractor_command = "7z" - - -- If the 7zz command exists - if shell_command_exists("7zz") then - -- - - -- Set the 7z command to the 7zz command - extractor_command = "7zz" - end - - -- Initialise the configuration object - local config = initialise_config(opts, { - extractor_command = extractor_command, - }, opts) - - -- Return the configuration object - return config -end - --- Function to standardise the mime type of a file. --- This function will follow what Yazi does to standardise --- mime types returned by the file command. ----@param mime_type string The mime type of the file ----@return string standardised_mime_type The standardised mime type of the file -local function standardise_mime_type(mime_type) - -- - - -- Trim the whitespace from the mime type - local trimmed_mime_type = string_trim(mime_type) - - -- Iterate over the mime type prefixes to remove - for _, prefix in ipairs(MIME_TYPE_PREFIXES_TO_REMOVE) do - -- - - -- Get the pattern to remove the mime type prefix - local pattern = - get_mime_type_without_prefix_template_pattern:format(prefix) - - -- Remove the prefix from the mime type - local mime_type_without_prefix, replacement_count = - trimmed_mime_type:gsub(pattern, "%1/%2") - - -- If the replacement count is greater than zero, - -- return the mime type without the prefix - if replacement_count > 0 then return mime_type_without_prefix end - end - - -- Return the mime type with whitespace removed - return trimmed_mime_type -end - --- Function to check if a given mime type is an archive ----@param mime_type string|nil The mime type of the file ----@return boolean is_archive Whether the mime type is an archive -local function is_archive_mime_type(mime_type) - -- - - -- If the mime type is nil, return false - if not mime_type then return false end - - -- Standardise the mime type - local standardised_mime_type = standardise_mime_type(mime_type) - - -- Get if the mime type is an archive - local is_archive = - table_get(ARCHIVE_MIME_TYPES, standardised_mime_type, false) - - -- Return if the mime type is an archive - return is_archive -end - --- Function to check if a given file extension --- is an archive file extension ----@param file_extension string|nil The file extension of the file ----@return boolean is_archive Whether the file extension is an archive -local function is_archive_file_extension(file_extension) - -- - - -- If the file extension is nil, return false - if not file_extension then return false end - - -- Make the file extension lower case - file_extension = file_extension:lower() - - -- Trim the whitespace from the file extension - file_extension = string_trim(file_extension) - - -- Get if the file extension is an archive - local is_archive = table_get(ARCHIVE_FILE_EXTENSIONS, file_extension, false) - - -- Return if the file extension is an archive file extension - return is_archive -end - --- Function to get the configuration from an async function ----@param state State The state object ----@return Configuration config The configuration object -local get_config = ya.sync(function(state) - -- - - -- Returns the configuration object - return state.config -end) - --- Function to get the current working directory ----@type fun(_): Url Returns the current working directory as a url -local get_current_directory_url = ya.sync( - function(_) return cx.active.current.cwd end -) - --- Function to get the path of the hovered item ----@param _ any ----@param quote boolean Whether to shell escape the characters in the path ----@return string|nil hovered_item_path The path of the hovered item -local get_path_of_hovered_item = ya.sync(function(_, quote) - -- - - -- Get the hovered item - local hovered_item = cx.active.current.hovered - - -- If there is no hovered item, exit the function - if not hovered_item then return end - - -- Convert the url of the hovered item to a string - local hovered_item_path = tostring(cx.active.current.hovered.url) - - -- If the quote flag is passed, - -- then quote the path of the hovered item - if quote then hovered_item_path = ya.quote(hovered_item_path) end - - -- Return the path of the hovered item - return hovered_item_path -end) - --- Function to get if the hovered item is a directory ----@param _ any ----@return boolean is_directory Whether the hovered item is a directory -local hovered_item_is_dir = ya.sync(function(_) - -- - - -- Get the hovered item - local hovered_item = cx.active.current.hovered - - -- Return if the hovered item exists and is a directory - return hovered_item and hovered_item.cha.is_dir -end) - --- Function to get if the hovered item is an archive ----@param _ any ----@return boolean is_archive Whether the hovered item is an archive -local hovered_item_is_archive = ya.sync(function(_) - -- - - -- Get the hovered item - local hovered_item = cx.active.current.hovered - - -- Return if the hovered item exists and is an archive - return hovered_item and is_archive_mime_type(hovered_item:mime()) -end) - --- Function to get the paths of the selected items ----@param _ any ----@param quote boolean Whether to shell escape the characters in the path ----@return string[]|nil paths The list of paths of the selected items -local get_paths_of_selected_items = ya.sync(function(_, quote) - -- - - -- Get the selected items - local selected_items = cx.active.selected - - -- If there are no selected items, exit the function - if #selected_items == 0 then return end - - -- Initialise the list of paths of the selected items - local paths_of_selected_items = {} - - -- Iterate over the selected items - for _, item in pairs(selected_items) do - -- - - -- Convert the url of the item to a string - local item_path = tostring(item) - - -- If the quote flag is passed, - -- then quote the path of the item - if quote then item_path = ya.quote(item_path) end - - -- Add the path of the item to the list of paths - table.insert(paths_of_selected_items, item_path) - end - - -- Return the list of paths of the selected items - return paths_of_selected_items -end) - --- Function to get the tab preferences ----@param _ State ----@return tab.Preference -local get_tab_preferences = ya.sync(function(_) - -- - - -- Create the table to store the tab preferences - local tab_preferences = {} - - -- Iterate over the tab preference keys - for key, _ in pairs(TAB_PREFERENCE_KEYS) do - -- - - -- Set the key in the table to the value - -- from the state - tab_preferences[key] = cx.active.pref[key] - end - - -- Return the tab preferences - return tab_preferences -end) - --- Function to get if Yazi is loading ----@type fun(_): boolean Returns whether Yazi is loading -local yazi_is_loading = ya.sync( - function(_) return cx.active.current.stage.is_loading end -) - --- Function to wait until Yazi is loaded ----@return nil -local function wait_until_yazi_is_loaded() - while yazi_is_loading() do - end -end - --- Function to choose which group of items to operate on. --- It returns ItemGroup.Hovered for the hovered item, --- ItemGroup.Selected for the selected items, --- and ItemGroup.Prompt to tell the calling function --- to prompt the user. ----@param state State The state object ----@return ItemGroup|nil item_group The desired item group -local get_item_group_from_state = ya.sync(function(state) - -- - - -- Get the hovered item - local hovered_item = cx.active.current.hovered - - -- The boolean representing that there are no selected items - local no_selected_items = #cx.active.selected == 0 - - -- If there is no hovered item - if not hovered_item then - -- - - -- If there are no selected items, exit the function - if no_selected_items then - return - - -- Otherwise, if the configuration is set to have a hovered item, - -- exit the function - elseif state.config.must_have_hovered_item then - return - - -- Otherwise, return the enum for the selected items - else - return ItemGroup.Selected - end - - -- Otherwise, there is a hovered item - -- and if there are no selected items, - -- return the enum for the hovered item. - elseif no_selected_items then - return ItemGroup.Hovered - - -- Otherwise if there are selected items and the user wants a prompt, - -- then tells the calling function to prompt them - elseif state.config.prompt then - return ItemGroup.Prompt - - -- Otherwise, if the hovered item is selected, - -- then return the enum for the selected items - elseif hovered_item:is_selected() then - return ItemGroup.Selected - - -- Otherwise, return the enum for the hovered item - else - return ItemGroup.Hovered - end -end) - --- Function to prompt the user for their desired item group ----@return ItemGroup|nil item_group The item group selected by the user -local function prompt_for_desired_item_group() - -- - - -- Get the configuration - local config = get_config() - - -- Get the default item group - local default_item_group = config.default_item_group_for_prompt - - -- Get the input options - local input_options = INPUT_OPTIONS_TABLE[default_item_group] - - -- If the default item group is None, then set it to nil - if default_item_group == ItemGroup.None then default_item_group = nil end - - -- Prompt the user for their input - local user_input, event = get_user_input( - "Operate on hovered or selected items? " .. input_options - ) - - -- If the user input is empty, then exit the function - if not user_input then return end - - -- Lowercase the user's input - user_input = user_input:lower() - - -- If the user did not confirm the input, exit the function - if event ~= 1 then - return - - -- Otherwise, if the user's input starts with "h", - -- return the item group representing the hovered item - elseif user_input:find("^h") then - return ItemGroup.Hovered - - -- Otherwise, if the user's input starts with "s", - -- return the item group representing the selected items - elseif user_input:find("^s") then - return ItemGroup.Selected - - -- Otherwise, return the default item group - else - return default_item_group - end -end - --- Function to get the item group ----@return ItemGroup|nil item_group The desired item group -local function get_item_group() - -- - - -- Get the item group from the state - local item_group = get_item_group_from_state() - - -- If the item group isn't the prompt one, - -- then return the item group immediately - if item_group ~= ItemGroup.Prompt then - return item_group - - -- Otherwise, prompt the user for the desired item group - else - return prompt_for_desired_item_group() - end -end - --- Function to get all the items in the given directory ----@param directory_url Url The url to the directory ----@param get_hidden_items boolean Whether to get hidden items ----@param directories_only boolean|nil Whether to only get directories ----@return Url[] directory_items The list of urls to the directory items -local function get_directory_items( - directory_url, - get_hidden_items, - directories_only -) - -- - - -- Initialise the list of directory items - local directory_items = {} - - -- Read the contents of the directory - local directory_contents, _ = fs.read_dir(directory_url, {}) - - -- If there are no directory contents, - -- then return the empty list of directory items - if not directory_contents then return directory_items end - - -- Iterate over the directory contents - for _, item in ipairs(directory_contents) do - -- - - -- If the get hidden items flag is set to false - -- and the item is a hidden item, - -- then continue the loop - if not get_hidden_items and item.cha.is_hidden then goto continue end - - -- If the directories only flag is passed - -- and the item is not a directory, - -- then continue the loop - if directories_only and not item.cha.is_dir then goto continue end - - -- Otherwise, add the item url to the list of directory items - table.insert(directory_items, item.url) - - -- The continue label to continue the loop - ::continue:: - end - - -- Return the list of directory items - return directory_items -end - --- Function to skip child directories with only one directory ----@param initial_directory_url Url The url of the initial directory ----@return nil -local function skip_single_child_directories(initial_directory_url) - -- - - -- Initialise the directory variable to the initial directory given - local directory = initial_directory_url - - -- Get the tab preferences - local tab_preferences = get_tab_preferences() - - -- Start an infinite loop - while true do - -- - - -- Get all the items in the current directory - local directory_items = - get_directory_items(directory, tab_preferences.show_hidden) - - -- If the number of directory items is not 1, - -- then break out of the loop. - if #directory_items ~= 1 then break end - - -- Otherwise, get the directory item - ---@type Url - local directory_item_url = table.unpack(directory_items) - - -- Get the cha object of the directory item - -- and don't follow symbolic links - local directory_item_cha = fs.cha(directory_item_url, false) - - -- If the cha object of the directory item is nil - -- then break the loop - if not directory_item_cha then break end - - -- If the directory item is not a directory, - -- break the loop - if not directory_item_cha.is_dir then break end - - -- Otherwise, set the directory to the inner directory - directory = directory_item_url - end - - -- Emit the change directory command to change to the directory variable - ya.manager_emit("cd", { directory }) -end - --- Function to check if an archive is password protected ----@param command_error_string string The error string from the extractor ----@return boolean is_encrypted Whether the archive is password protected -local function archive_is_encrypted(command_error_string) - -- - - -- Return true if the string contains the word "wrong password", - -- and false otherwise - if command_error_string:lower():find("wrong password", 1, true) then - return true - else - return false - end -end - --- Function to handle retrying the extractor command --- --- The initial password is the password given to the extractor command --- and the test encryption is to test the archive password without --- actually executing the given extractor command. ----@param extractor_function ExtractorFunction Function to run the extractor ----@param config Configuration The configuration object ----@param initial_password string|nil The initial password to try ----@param archive_path string|nil The path to the archive file ----@return boolean successful Whether the extraction was successful ----@return string|nil error_message An error message for unsuccessful extracts ----@return string|nil stdout The standard output of the extractor command ----@return string|nil correct_password The correct password to the archive -local function retry_extractor( - extractor_function, - config, - initial_password, - archive_path -) - -- - - -- Initialise the password to the initial password - -- or an empty string if it's not given - local password = initial_password or "" - - -- Initialise the archive path to the given archive path - -- or an empty string if it's not given - archive_path = archive_path or "" - - -- Initialise the error message from the archive extractor - local error_message = "" - - -- Initialise the number of tries - -- to the number of retries plus 1 - local total_number_of_tries = config.extract_retries + 1 - - -- Initialise the initial password prompt - local initial_password_prompt = - "Archive is encrypted, please enter the password:" - - -- Initialise the wrong password prompt - local wrong_password_prompt = - "Wrong password, please enter another password:" - - -- Iterate over the number of times to try the extraction - for tries = 0, total_number_of_tries do - -- - - -- Execute the extractor command - local output, err = extractor_function(password, config) - - -- If there is no output - -- then return false, the error as a string, - -- nil for the output, and nil for the password - if not output then return false, tostring(err), nil, nil end - - -- If the output was 0, which means the extractor command was successful - if output.status.code == 0 then - -- - - -- Initialise the correct password to nil - local correct_password = nil - - -- If the password is not empty, - -- then set the correct password to the password - if string.len(string_trim(password)) > 0 then - correct_password = password - end - - -- Return true, nil for the error message, - -- the standard output of the output, - -- and the correct password - return true, nil, output.stdout, correct_password - end - - -- Set the error message to the standard error - -- from the archive extractor - error_message = output.stderr - - -- If the command failed for some other reason other - -- than the archive being encrypted, then return false, - -- the error message, the standard output of the output, - -- and nil for the password to the archive - if - not ( - output.status.code == 2 and archive_is_encrypted(output.stderr) - ) - then - return false, error_message, output.stdout, nil - end - - -- If it is the last try, then return false - -- and the error message, the standard output of the output, - -- and nil for the password to the archive. - if tries == total_number_of_tries then - return false, error_message, output.stdout, nil - end - - -- Ask the user for the password - local user_input, event = get_user_input( - tries == 0 and initial_password_prompt or wrong_password_prompt - ) - - -- If the user has confirmed the input, - -- and the user input is not nil, - -- set the password to the user's input - if event == 1 and user_input ~= nil then - password = user_input - - -- Otherwise, return false, the error message, - -- the standard output of the output, - -- and nil for the password to the archive - -- as the user has cancelled the prompt, - -- or an unknown error has occurred - else - return false, error_message, output.stdout, nil - end - end - - -- If all the tries have been exhausted, - -- then return false, the error message - -- and nil - return false, error_message, nil, nil -end - --- The command to list the items in an archive ----@param archive_path string The path to the archive ----@param config Configuration The configuration object ----@param password string|nil The password to the archive ----@param remove_headers boolean|nil Whether to remove the headers ----@param show_details boolean|nil Whether to show the details ----@return CommandOutput|nil, integer -local function list_archive_items_command( - archive_path, - config, - password, - remove_headers, - show_details -) - -- - - -- Initialise the password to an empty string if it's not given - password = password or "" - - -- Initialise the remove headers flag to false if it's not given - remove_headers = remove_headers or false - - -- Initialise the show details flag to false if it's not given - show_details = show_details or false - - -- Initialise the arguments for the command - local arguments = { - - -- List the items in the archive - "l", - - -- Use UTF-8 encoding for console input and output - "-sccUTF-8", - - -- Pass the password to the command - "-p" .. password, - } - - -- If the remove headers flag is passed - if remove_headers then - -- - - -- Add the switch to remove the headers (undocumented switch) - table.insert(arguments, "-ba") - end - - -- If the show details flag is passed - if show_details then - -- - - -- Add the switch to show the details - table.insert(arguments, "-slt") - end - - -- Add the archive path to the arguments - table.insert(arguments, archive_path) - - -- Return the result of the command to list the items in the archive - return Command(config.extractor_command) - :args(arguments) - :stdout(Command.PIPED) - :stderr(Command.PIPED) - :output() -end - --- Function to get if the archive --- file has more than one file in it. ----@param archive_path string The path to the archive file ----@param config Configuration The configuration object ----@return table files The list of files in the archive ----@return table directories The list of directories in the archive ----@return string|nil error_message The error message for an incorrect password ----@return string|nil correct_password The correct password to the archive -local function get_archive_items(archive_path, config) - -- - - -- Function to list the items in the archive - local function list_items_in_archive(password, configuration, _) - return list_archive_items_command( - archive_path, - configuration, - password, - true - ) - end - - -- Initialise the list of files in the archive - ---@type string[] - local files = {} - - -- Initialise the list of directories - ---@type string[] - local directories = {} - - -- Call the function to retry the extractor command - -- with the list items in the archive function - local successful, error_message, output, password = - retry_extractor(list_items_in_archive, config) - - -- If the extractor command was not successful, - -- or the output was nil, - -- then return nil the error message, - -- and nil as the correct password - if not successful or not output then - return files, directories, error_message, nil - end - - -- Otherwise, split the output at the newline character - local output_lines = string_split(output, "\n") - - -- Iterate over the lines of the output - for _, line in ipairs(output_lines) do - -- - - -- Get the information about the archive item from the line. - -- The information is in the format: - -- Attributes, Size, Compressed Size, File Path - local attributes, _, _, file_path = - line:match(archive_item_info_pattern) - - -- If the file path doesn't exist, then continue the loop - if not file_path then goto continue end - - -- If the attributes of the item starts with a "D", - -- which means the item is a directory - if attributes and attributes:find("^D") then - -- - - -- Add the directory to the list of directories - table.insert(directories, file_path) - - -- Continue the loop - goto continue - end - - -- Otherwise, add the file path to the list of archive items - table.insert(files, file_path) - - -- The continue label to continue the loop - ::continue:: - end - - -- Return the list of files, the list of directories, - -- the error message, and the password - return files, directories, error_message, password -end - --- Function to get a temporary name. --- The code is taken from Yazi's source code. ----@param path string The path to the item to create a temporary name ----@return string temporary_name The temporary name for the item -local function get_temporary_name(path) - return ".tmp_" - .. ya.hash(string.format("extract//%s//%.10f", path, ya.time())) -end - --- Function to get a temporary directory url --- for the given file path ----@param path string The path to the item to create a temporary directory ----@return Url|nil url The url of the temporary directory -local function get_temporary_directory_url(path) - -- - - -- Get the parent directory of the file path - ---@type Url - local parent_directory = Url(path):parent() - - -- If the parent directory doesn't exist, then return nil - if not parent_directory then return nil end - - -- Otherwise, create the temporary directory path - local temporary_directory_url = - fs.unique_name(parent_directory:join(get_temporary_name(path))) - - -- Return the temporary directory path - return temporary_directory_url -end - --- The extract command to extract an archive ----@param archive_path string The path to the archive ----@param destination_directory_path string The destination folder ----@param config Configuration The configuration object ----@param password string|nil The password to the archive ----@param extract_files_only boolean|nil Extract the files only or not ----@param extract_behaviour ExtractBehaviour|nil The extraction behaviour ----@return CommandOutput|nil, integer -local function extract_command( - archive_path, - destination_directory_path, - config, - password, - extract_files_only, - extract_behaviour -) - -- - - -- Initialise the password to an empty string if it's not given - password = password or "" - - -- Initialise the extract files only flag to false if it's not given - extract_files_only = extract_files_only or false - - -- Initialise the extract behaviour to rename if it's not given - extract_behaviour = extract_behaviour or ExtractBehaviour.Rename - - -- Initialise the extraction mode to use. - -- By default, it extracts the archive with - -- full paths, which keeps the archive structure. - local extraction_mode = "x" - - -- If the extract files only flag is passed - if extract_files_only then - -- - - -- Use the regular extract, - -- without the full paths, which will move - -- all files in the archive into the current directory - -- and ignore the archive folder structure. - extraction_mode = "e" - end - - -- Initialise the arguments for the command - local arguments = { - - -- The extraction mode - extraction_mode, - - -- Assume yes to all prompts - "-y", - - -- Use UTF-8 encoding for console input and output - "-sccUTF-8", - - -- Configure the extraction behaviour - extract_behaviour, - - -- Pass the password to the command - "-p" .. password, - - -- The archive file to extract - archive_path, - - -- The destination directory path - "-o" .. destination_directory_path, - } - - -- Return the command to extract the archive - return Command(config.extractor_command) - :args(arguments) - :stdout(Command.PIPED) - :stderr(Command.PIPED) - :output() -end - --- Function to get the mime type of a file ----@param file_path string The path to the file ----@return string mime_type The mime type of the file -local function get_mime_type(file_path) - -- - - -- Get the output of the file command - local output, _ = Command("file") - :args({ - - -- Don't prepend file names to the output - "-b", - - -- Print the mime type of the file - "--mime-type", - - -- The file path to get the mime type of - file_path, - }) - :stdout(Command.PIPED) - :stderr(Command.PIPED) - :output() - - -- If there is no output, then return an empty string - if not output then return "" end - - -- Otherwise, get the mime type from the standard output - local mime_type = string_trim(output.stdout) - - -- Return the mime type - return mime_type -end - --- Function to check if a file is an archive ----@param file_path string The path to the file ----@return boolean is_archive Whether the file is an archive -local function is_archive_file(file_path) - -- - - -- Initialise the is archive variable to false - local is_archive = false - - -- Call the function to get the mime type of the file - local mime_type = get_mime_type(file_path) - - -- Set the is archive variable - is_archive = is_archive_mime_type(mime_type) - - -- Return the is archive variable - return is_archive -end - --- Function to clean up the temporary directory --- after extracting an archive. ----@param temporary_directory_url Url The url of the temporary directory ----@param removal_mode "dir" | "dir_all" | "dir_clean" The removal mode ----@param ... any Return values ----@return ... Returns the given return values -local function clean_up_temporary_directory( - temporary_directory_url, - removal_mode, - ... -) - -- - - -- Remove the temporary directory - fs.remove(removal_mode, temporary_directory_url) - - -- Return the given return values - return ... -end - --- Function to move extracted items out of the temporary directory ----@param archive_url Url The url of the archive ----@param temporary_directory_url Url The url of the temporary directory ----@return boolean move_successful Whether the move was successful ----@return string|nil error_message An error message for unsuccessful extracts ----@return string|nil extracted_items_path The path of the extracted item -local function move_extracted_items_to_archive_parent_directory( - archive_url, - temporary_directory_url -) - -- - - -- Initialise whether or not the move is successful to false - local move_successful = false - - -- Initialise the path of the extracted items - local extracted_items_path = nil - - -- Get the extracted items in the directory - -- containing the extracted items. - -- There is a limit of 2 as there should only be - -- a single item in the directory. - local extracted_items = fs.read_dir(temporary_directory_url, { limit = 2 }) - - -- If the extracted items doesn't exist, - -- clean up the temporary directory and - -- return that the move successful variable - -- the error message, and the extracted item path - if not extracted_items then - return clean_up_temporary_directory( - temporary_directory_url, - "dir_all", - move_successful, - "Failed to read the temporary directory", - extracted_items_path - ) - end - - -- If there are no extracted items, - -- clean up the temporary directory and - -- return that the move successful variable - -- the error message, and the extracted item path - if #extracted_items == 0 then - return clean_up_temporary_directory( - temporary_directory_url, - "dir", - move_successful, - "No files extracted from the archive", - extracted_items_path - ) - end - - -- Get the parent directory url of the archive - local parent_directory_url = archive_url:parent() - - -- If the parent directory url is nil, - -- then return the move successful variable, - -- the error message, and the extracted item path - if not parent_directory_url then - return clean_up_temporary_directory( - temporary_directory_url, - "dir_all", - move_successful, - "Parent directory doesn't exist", - extracted_items_path - ) - end - - -- Get the file name of the archive without the extension - local archive_file_name = archive_url:stem() - - -- If the archive file name is nil, - -- then return the move successful variable, - -- the error message, and the extracted item path - if not archive_file_name then - return clean_up_temporary_directory( - temporary_directory_url, - "dir_all", - move_successful, - "Archive's file name is empty", - extracted_items_path - ) - end - - -- Get the first extracted item - local first_extracted_item = table.unpack(extracted_items) - - -- Get the url of the first extracted item - local first_extracted_item_url = first_extracted_item.url - - -- Initialise the variable to - -- store whether there is only - -- a single file in the archive - local only_one_item_in_archive = false - - -- Initialise the target directory url to move the extracted items to, - -- which is the parent directory of the archive - -- joined with the file name of the archive without the extension - local target_url = parent_directory_url:join(archive_file_name) - - -- If there is only one item in the archive - if #extracted_items == 1 then - -- - - -- Set the only one item in archive variable to true - only_one_item_in_archive = true - - -- Get the name of the first extracted item - local first_extracted_item_name = first_extracted_item_url:name() - - -- If the first extracted item name is nil, - -- then clean up the temporary directory - -- and exit the function - if not first_extracted_item_name then - return clean_up_temporary_directory( - temporary_directory_url, - "dir_all", - move_successful, - "Failed to get a name for the extracted item.", - extracted_items_path - ) - end - - -- Set the target url to the parent directory of the archive - -- joined with the file name of the extracted item - target_url = parent_directory_url:join(first_extracted_item_name) - end - - -- Get a unique name for the target url - local unique_target_url = fs.unique_name(target_url) - - -- If the unique target url is nil somehow, - -- clean up the temporary directory and - -- return the move successful variable, - -- the error message and the extracted item path - if not unique_target_url then - return clean_up_temporary_directory( - temporary_directory_url, - "dir_all", - move_successful, - "Failed to get a unique name to move the extracted items to", - extracted_items_path - ) - end - - -- Otherwise, set the target url to the unique target url - target_url = unique_target_url - - -- Set the extracted items path to the target path - extracted_items_path = tostring(target_url) - - -- Initialise the error message to nil - local error_message = nil - - -- If there is only one item in the archive - if only_one_item_in_archive then - -- - - -- Move the item to the target path - move_successful, error_message = - os.rename(tostring(first_extracted_item_url), extracted_items_path) - - -- Otherwise - else - -- - - -- Rename the temporary directory itself to the target path - move_successful, error_message = - os.rename(tostring(temporary_directory_url), extracted_items_path) - end - - -- Clean up the temporary directory - -- and return if the move was successful - -- the error message and the extracted item path - return clean_up_temporary_directory( - temporary_directory_url, - move_successful and "dir" or "dir_all", - move_successful, - error_message, - extracted_items_path - ) -end - ---- Function to extract an archive. ----@param archive_path string The path to the archive ----@param config Configuration The configuration object ----@param has_only_one_file boolean Whether the archive has only one file ----@param initial_password string|nil The initial password to try ----@return ExtractionResult extraction_result The result of the extraction -local function extract_archive( - archive_path, - config, - has_only_one_file, - initial_password -) - -- - - -- Initialise the successful variable to false - local successful = false - - -- Initialise the error message to nil - local error_message = nil - - -- Initialise the extracted items path to nil - local extracted_items_path = nil - - -- Get the url of the temporary directory - local temporary_directory_url = get_temporary_directory_url(archive_path) - - -- If the temporary directory url is nil, - -- then return the successful variable, an error message - -- saying a path for the temporary directory - -- cannot be determined, and the extracted items path - if not temporary_directory_url then - return { - archive_path = archive_path, - successful = successful, - extracted_items_path = extracted_items_path, - error_message = "Failed to determine a path " - .. "for the temporary directory", - } - end - - -- Get the url of the archive - ---@type Url - local archive_url = Url(archive_path) - - -- Get the name of the archive - local archive_name = archive_url:stem() - - -- If the archive name is nil, - -- then return the successful variable, - -- an error message saying - -- that the archive file name is somehow empty, - -- and the extracted items path - if not archive_name then - return { - archive_path = archive_path, - successful = successful, - extracted_items_path = extracted_items_path, - error_message = "Archive file name is empty", - } - end - - -- Create the extractor command - local function extractor_command(password, configuration) - return extract_command( - archive_path, - tostring(temporary_directory_url), - configuration, - password, - has_only_one_file, - ExtractBehaviour.Overwrite - ) - end - - -- Call the function to retry the extractor command - successful, error_message, _, _ = retry_extractor( - extractor_command, - config, - initial_password, - archive_path - ) - - -- If the extraction was not successful, - if not successful then - -- - - -- Clean up the temporary directory - clean_up_temporary_directory(temporary_directory_url, "dir_all") - - -- Return the extraction results - return { - archive_path = archive_path, - successful = successful, - extracted_items_path = extracted_items_path, - error_message = error_message, - } - end - - -- Otherwise, move the extracted items - -- to the parent directory of the archive - successful, error_message, extracted_items_path = - move_extracted_items_to_archive_parent_directory( - archive_url, - temporary_directory_url - ) - - -- Create the extraction result - ---@type ExtractionResult - local extraction_result = { - archive_path = archive_path, - successful = successful, - extracted_items_path = extracted_items_path, - error_message = error_message, - } - - -- Return the result of the extraction - return extraction_result -end - --- Function to recursively extract archives ----@param archive_path string The path to the archive ----@param config Configuration The configuration object ----@return ExtractionResult[] extraction_results The list of extraction results ----@return string|nil extracted_items_path The path to the extracted items -local function recursively_extract_archives(archive_path, config) - -- - - -- Initialise the table of extraction results - ---@type ExtractionResult[] - local list_of_extraction_results = {} - - -- Get the list of archive files and directories, - -- the error message and the password - local archive_files, archive_directories, err, password = - get_archive_items(archive_path, config) - - -- If there are no are no archive files and directories - if #archive_files == 0 and #archive_directories == 0 then - -- - - -- Add that the archive is empty if there is no error message - table.insert(list_of_extraction_results, { - archive_path = archive_path, - successful = false, - error_message = err or "Archive is empty", - }) - - -- Return the list of extraction results - return list_of_extraction_results - end - - -- Get if the archive has only one file - local archive_has_only_one_file = #archive_files == 1 - and #archive_directories == 0 - - -- Extract the given archive - local extraction_results = extract_archive( - archive_path, - config, - archive_has_only_one_file, - password - ) - - -- Add the extraction results to the list of extraction results - table.insert(list_of_extraction_results, extraction_results) - - -- Get the extracted items path - local extracted_items_path = extraction_results.extracted_items_path - - -- If the extraction of the archive isn't successful, - -- or if the extracted items path is nil, - -- or if the user does not want to extract archives recursively, - -- return the list of extraction results - if - not extraction_results.successful - or not extracted_items_path - or not config.recursively_extract_archives - then - return list_of_extraction_results, extracted_items_path - end - - -- Get the url of the extracted items path - ---@type Url - local extracted_items_url = Url(extracted_items_path) - - -- Initialise the base url for the extracted items - local base_url = extracted_items_url - - -- Get the parent directory of the extracted items path - local parent_directory_url = extracted_items_url:parent() - - -- If the parent directory doesn't exist, - -- then return the list of extraction results - if not parent_directory_url then return list_of_extraction_results end - - -- If the archive has only one file - if archive_has_only_one_file then - -- - - -- Set the base url to the parent directory of the extracted items path - base_url = parent_directory_url - end - - -- Iterate over the archive files - for _, file in ipairs(archive_files) do - -- - - -- Get the file extension of the file - local file_extension = file:match(file_extension_pattern) - - -- If the file extension is not found, then skip the file - if not file_extension then goto continue end - - -- If the file extension is not an archive file extension, skip the file - if not is_archive_file_extension(file_extension) then goto continue end - - -- Otherwise, get the full url to the archive - local full_archive_url = base_url:join(file) - - -- Get the full path to the archive - local full_archive_path = tostring(full_archive_url) - - -- If the file is not an archive, skip the file - if not is_archive_file(full_archive_path) then goto continue end - - -- Otherwise, recursively extract the archive - local archive_extraction_results, extracted_archive_path = - recursively_extract_archives(full_archive_path, config) - - -- Merge the results with the existing list of extraction results - list_of_extraction_results = - merge_tables(list_of_extraction_results, archive_extraction_results) - - -- If the archive has only one file, - -- update the extracted items path - -- to the extracted archive path - if archive_has_only_one_file then - extracted_items_path = extracted_archive_path - end - - -- Remove the archive file after extracting it - fs.remove("file", full_archive_url) - - -- The label the continue the loop - ::continue:: - end - - -- Return the list of extraction results and the extracted items path - return list_of_extraction_results, extracted_items_path -end - --- Function to handle the open command ----@type CommandFunction -local function handle_open(args, config, command_table) - -- - - -- Call the function to get the item group - local item_group = get_item_group() - - -- If no item group is returned, exit the function - if not item_group then return end - - -- If the item group is the selected items, - -- then execute the command and exit the function - if item_group == ItemGroup.Selected then - -- - - -- Emit the command and exit the function - return ya.manager_emit("open", args) - end - - -- Otherwise, the item group is the hovered item. - -- Get the function to handle the enter command. - local enter_command = command_table[Commands.Enter] - - -- If the hovered item is a directory - if hovered_item_is_dir() then - -- - - -- If smart enter is wanted, - -- calls the function to enter the directory - -- and exit the function - if config.smart_enter or table_pop(args, "smart", false) then - return enter_command(args, config, command_table) - end - - -- Otherwise, just exit the function - return - end - - -- Otherwise, if the hovered item is not an archive, - -- or entering archives isn't wanted, - -- or the interactive flag is passed - if - not hovered_item_is_archive() - or not config.enter_archives - or args.interactive - then - -- - - -- Simply emit the open command, - -- opening only the hovered item - -- as the item group is the hovered item, - -- and exit the function - return ya.manager_emit("open", merge_tables(args, { hovered = true })) - end - - -- Otherwise, the hovered item is an archive - -- and entering archives is wanted, - -- so get the path of the hovered item - local archive_path = get_path_of_hovered_item() - - -- If the archive path somehow doesn't exist, then exit the function - if not archive_path then return end - - -- Run the function to extract the archive - local extraction_results, extracted_items_path = - recursively_extract_archives(archive_path, config) - - -- Iterate over the extraction results - for _, extraction_result in ipairs(extraction_results) do - -- - - -- If the extraction is not successful, notify the user - if not extraction_result.successful then - show_error( - "Failed to extract archive at: " - .. extraction_result.archive_path - .. "\nError: " - .. extraction_result.error_message - ) - end - end - - -- If the extracted items path is nil, - -- then exit the function - if not extracted_items_path then return end - - -- Get the cha object of the extracted items path - local extracted_items_cha = fs.cha(Url(extracted_items_path), false) - - -- If the cha object of the extracted items path is nil - -- then exit the function - if not extracted_items_cha then return end - - -- If the extracted items path is not a directory, - -- then exit the function - if not extracted_items_cha.is_dir then return end - - -- Enter the archive directory - ya.manager_emit("cd", { extracted_items_path }) - - -- If the user doesn't want to skip single subdirectories on enter, - -- or one of the arguments passed is no skip, - -- then exit the function - if - not config.skip_single_subdirectory_on_enter - or table_pop(args, "no_skip", false) - then - return - end - - -- Calls the function to skip child directories - -- with only a single directory inside - skip_single_child_directories(Url(extracted_items_path)) -end - --- Function to handle the enter command ----@type CommandFunction -local function handle_enter(args, config, command_table) - -- - - -- Get the function for the open command - local open_command = command_table[Commands.Open] - - -- If the hovered item is not a directory - if not hovered_item_is_dir() then - -- - - -- If smart enter is wanted, - -- call the function for the open command - -- and exit the function - if config.smart_enter or table_pop(args, "smart", false) then - return open_command(args, config, command_table) - end - - -- Otherwise, just exit the function - return - end - - -- Otherwise, always emit the enter command, - ya.manager_emit("enter", args) - - -- If the user doesn't want to skip single subdirectories on enter, - -- or one of the arguments passed is no skip, - -- then exit the function - if - not config.skip_single_subdirectory_on_enter - or table_pop(args, "no_skip", false) - then - return - end - - -- Otherwise, call the function to skip child directories - -- with only a single directory inside - skip_single_child_directories(get_current_directory_url()) -end - --- Function to handle the leave command ----@type CommandFunction -local function handle_leave(args, config) - -- - - -- Always emit the leave command - ya.manager_emit("leave", args) - - -- If the user doesn't want to skip single subdirectories on leave, - -- or one of the arguments passed is no skip, - -- then exit the function - if - not config.skip_single_subdirectory_on_leave - or table_pop(args, "no_skip", false) - then - return - end - - -- Otherwise, initialise the directory to the current directory - ---@type Url - local directory = get_current_directory_url() - - -- Get the tab preferences - local tab_preferences = get_tab_preferences() - - -- Start an infinite loop - while true do - -- - - -- Get all the items in the current directory - local directory_items = - get_directory_items(directory, tab_preferences.show_hidden) - - -- If the number of directory items is not 1, - -- then break out of the loop. - if #directory_items ~= 1 then break end - - -- Get the parent directory of the current directory - ---@type Url|nil - local parent_directory = directory:parent() - - -- If the parent directory is nil, - -- break the loop - if not parent_directory then break end - - -- Otherwise, set the new directory to the parent directory - directory = parent_directory - end - - -- Emit the change directory command to change to the directory variable - ya.manager_emit("cd", { directory }) -end - --- Function to handle a Yazi command ----@param command string A Yazi command ----@param args Arguments The arguments passed to the plugin ----@return nil -local function handle_yazi_command(command, args) - -- - - -- Call the function to get the item group - local item_group = get_item_group() - - -- If no item group is returned, exit the function - if not item_group then return end - - -- If the item group is the selected items - if item_group == ItemGroup.Selected then - -- - - -- Emit the command to operate on the selected items - ya.manager_emit(command, args) - - -- If the item group is the hovered item - elseif item_group == ItemGroup.Hovered then - -- - - -- Emit the command with the hovered option - ya.manager_emit(command, merge_tables(args, { hovered = true })) - end -end - --- Function to enter or open the created file ----@param item_url Url The url of the item to create ----@param is_directory boolean|nil Whether the item to create is a directory ----@param args Arguments The arguments passed to the plugin ----@param config Configuration The configuration object ----@return nil -local function enter_or_open_created_item(item_url, is_directory, args, config) - -- - - -- If the item is a directory - if is_directory then - -- - - -- If user does not want to enter the directory - -- after creating it, exit the function - if - not ( - config.enter_directory_after_creation - or table_pop(args, "enter", false) - ) - then - return - end - - -- Otherwise, call the function change to the created directory - return ya.manager_emit("cd", { tostring(item_url) }) - end - - -- Otherwise, the item is a file - - -- If the user does not want to open the file - -- after creating it, exit the function - if - not (config.open_file_after_creation or table_pop(args, "open", false)) - then - return - end - - -- Otherwise, call the function to reveal the created file - ya.manager_emit("reveal", { tostring(item_url) }) - - -- Wait for Yazi to finish loading - wait_until_yazi_is_loaded() - - -- Call the function to open the file - return ya.manager_emit("open", { hovered = true }) -end - --- Function to execute the create command ----@param item_url Url The url of the item to create ----@param args Arguments The arguments passed to the plugin ----@param config Configuration The configuration object ----@return nil -local function execute_create(item_url, is_directory, args, config) - -- - - -- Get the parent directory of the file to create - local parent_directory_url = item_url:parent() - - -- If the parent directory doesn't exist, - -- then show an error and exit the function - if not parent_directory_url then - return show_error( - "Parent directory of the item to create doesn't exist" - ) - end - - -- If the item to create is a directory - if is_directory then - -- - - -- Call the function to create the directory - local successful, error_message = fs.create("dir_all", item_url) - - -- If the function is not successful, - -- show the error message and exit the function - if not successful then return show_error(tostring(error_message)) end - - -- Otherwise, the item to create is a file - else - -- - - -- Otherwise, create the parent directory if it doesn't exist - if not fs.cha(parent_directory_url, false) then - -- - - -- Call the function to create the parent directory - local successful, error_message = - fs.create("dir_all", parent_directory_url) - - -- If the function is not successful, - -- show the error message and exit the function - if not successful then - return show_error(tostring(error_message)) - end - end - - -- Otherwise, create the file - local successful, error_message = fs.write(item_url, "") - - -- If the function is not successful, - -- show the error message and exit the function - if not successful then return show_error(tostring(error_message)) end - end - - -- Call the function to enter or open the created item - enter_or_open_created_item(item_url, is_directory, args, config) -end - --- Function to handle the create command ----@type CommandFunction -local function handle_create(args, config) - -- - - -- Get the directory flag - local dir_flag = table_pop(args, "dir", false) - - -- Get the user's input for the item to create - local user_input, event = - get_user_input(dir_flag and "Create (dir):" or "Create:") - - -- If the user input is nil, - -- or if the user did not confirm the input, - -- exit the function - if not user_input or event ~= 1 then return end - - -- Get the current working directory as a url - ---@type Url - local current_working_directory = get_current_directory_url() - - -- Get whether the url ends with a path delimiter - local ends_with_path_delimiter = user_input:find("[/\\]$") - - -- Get the whether the given item is a directory or not based - -- on the default conditions for a directory - local is_directory = ends_with_path_delimiter or dir_flag - - -- Get the url from the user's input - ---@type Url - local item_url = Url(user_input) - - -- If the user does not want to use the default Yazi create behaviour - if - not ( - config.use_default_create_behaviour - or table_pop(args, "default_behaviour", false) - ) - then - -- - - -- Get the file extension from the user's input - local file_extension = user_input:match(file_extension_pattern) - - -- Set the is directory variable to the is directory condition - -- or if the file extension exists - is_directory = is_directory or not file_extension - end - - -- Get the full url of the item to create - local full_url = current_working_directory:join(item_url) - - -- If the path to the item to create already exists, - -- and the user did not pass the force flag - if fs.cha(full_url, false) and not table_pop(args, "force", false) then - -- - - -- Get the user's confirmation for - -- whether they want to overwrite the item - local user_confirmation = get_user_confirmation( - - -- TODO: Remove the line below - "The item already exists, overwrite? (y/N)", - "Overwrite file?", - ui.Text({ - ui.Line("Will overwrite the following file:") - :align(ui.Line.CENTER), - ui.Line(string.rep("-", DEFAULT_CONFIRM_OPTIONS.pos.w - 2)) - :align(ui.Line.LEFT), - ui.Line(tostring(full_url)):align(ui.Line.LEFT), - }):wrap(ui.Text.WRAP_TRIM) - ) - - -- If the user did not confirm the overwrite, - -- then exit the function - if not user_confirmation then return end - end - - -- Call the function to execute the create command - return execute_create(full_url, is_directory, args, config) -end - --- Function to remove the F flag from the less command ----@param command string The shell command containing the less command ----@return string command The command with the F flag removed ----@return boolean f_flag_found Whether the F flag was found -local function remove_f_flag_from_less_command(command) - -- - - -- Initialise the variable to store if the F flag is found - local f_flag_found = false - - -- Initialise the variable to store the replacement count - local replacement_count = 0 - - -- Remove the F flag when it is passed at the start - -- of the flags given to the less command - command, replacement_count = command:gsub("(%f[%a]less%f[%A].*)%-F", "%1") - - -- If the replacement count is not 0, - -- set the f_flag_found variable to true - if replacement_count ~= 0 then f_flag_found = true end - - -- Remove the F flag when it is passed in the middle - -- or end of the flags given to the less command command - command, replacement_count = - command:gsub("(%f[%a]less%f[%A].*%-)(%a*)F(%a*)", "%1%2%3") - - -- If the replacement count is not 0, - -- set the f_flag_found variable to true - if replacement_count ~= 0 then f_flag_found = true end - - -- Return the command and whether or not the F flag was found - return command, f_flag_found -end - --- Function to fix a command containing less. --- All this function does is remove --- the F flag from a command containing less. ----@param command string The shell command containing the less command ----@return string command The fixed shell command -local function fix_shell_command_containing_less(command) - -- - - -- Remove the F flag from the given command - local fixed_command = remove_f_flag_from_less_command(command) - - -- Get the LESS environment variable - local less_environment_variable = os.getenv("LESS") - - -- If the LESS environment variable is not set, - -- then return the given command with the F flag removed - if not less_environment_variable then return fixed_command end - - -- Otherwise, remove the F flag from the LESS environment variable - -- and check if the F flag was found - local less_command_with_modified_env_variables, f_flag_found = - remove_f_flag_from_less_command("less " .. less_environment_variable) - - -- If the F flag isn't found, - -- then return the given command with the F flag removed - if not f_flag_found then return fixed_command end - - -- Add the less environment variable flags to the less command - fixed_command = fixed_command:gsub( - "%f[%a]less%f[%A]", - escape_replacement_string(less_command_with_modified_env_variables) - ) - - -- Unset the LESS environment variable before calling the command - fixed_command = "unset LESS; " .. fixed_command - - -- Return the fixed command - return fixed_command -end - --- Function to fix the bat default pager command ----@param command string The command containing the bat default pager command ----@return string command The fixed bat command -local function fix_bat_default_pager_shell_command(command) - -- - - -- Initialise the default pager command for bat without the F flag - local bat_default_pager_command_without_f_flag = "less -RX" - - -- Get the modified command and the replacement count - -- when replacing the less command when it is quoted - local modified_command, replacement_count = command:gsub( - "(" - .. bat_command_with_pager_pattern - .. "['\"]+%s*" - .. ")" - .. "less" - .. "(%s*['\"]+)", - "%1" .. bat_default_pager_command_without_f_flag .. "%2" - ) - - -- If the replacement count is not 0, - -- then return the modified command - if replacement_count ~= 0 then return modified_command end - - -- Otherwise, get the modified command and the replacement count - -- when replacing the less command when it is unquoted - modified_command, replacement_count = command:gsub( - "(" .. bat_command_with_pager_pattern .. ")" .. "less", - '%1"' .. bat_default_pager_command_without_f_flag .. '"' - ) - - -- If the replacement count is not 0, - -- then return the modified command - if replacement_count ~= 0 then return modified_command end - - -- Otherwise, return the given command - return command -end - --- Function to fix the shell commands given to work properly with Yazi ----@param command string A shell command ----@return string command The fixed shell command -local function fix_shell_command(command) - -- - - -- If the given command includes the less command - if command:find("%f[%a]less%f[%A]") ~= nil then - -- - - -- Fix the command containing less - command = fix_shell_command_containing_less(command) - end - - -- If the given command contains the bat command with the pager - -- option passed - if command:find(bat_command_with_pager_pattern) ~= nil then - -- - - -- Calls the command to fix the bat command with the default pager - command = fix_bat_default_pager_shell_command(command) - end - - -- Return the modified command - return command -end - --- Function to handle a shell command ----@type CommandFunction -local function handle_shell(args, _, _) - -- - - -- Get the first item of the arguments given - -- and set it to the command variable - local command = table.remove(args, 1) - - -- Get the type of the command variable - local command_type = type(command) - - -- If the command isn't a string, - -- show an error message and exit the function - if command_type ~= "string" then - return show_error( - string.format( - "Shell command given is not a string, " - .. "instead it is a '%s', " - .. "with value '%s'", - command_type, - tostring(command) - ) - ) - end - - -- Fix the given command - command = fix_shell_command(command) - - -- Call the function to get the item group - local item_group = get_item_group() - - -- If no item group is returned, exit the function - if not item_group then return end - - -- Get whether the exit if directory flag is passed - local exit_if_dir = table_pop(args, "exit_if_dir", false) - - -- If the item group is the selected items - if item_group == ItemGroup.Selected then - -- - - -- If the exit if directory flag is passed - if exit_if_dir then - -- - - -- Initialise the number of files - local number_of_files = 0 - - -- Iterate over all of the selected items - for _, item in pairs(get_paths_of_selected_items()) do - -- - - -- Get the cha object of the item - local item_cha = fs.cha(Url(item), false) - - -- If the item isn't a directory - if not (item_cha or {}).is_dir then - -- - - -- Increment the number of files - number_of_files = number_of_files + 1 - end - end - - -- If the number of files is 0, then exit the function - if number_of_files == 0 then return end - end - - -- Replace the shell variable in the command - -- with the quoted paths of the selected items - command = command:gsub( - shell_variable_pattern, - escape_replacement_string( - table.concat(get_paths_of_selected_items(true), " ") - ) - ) - - -- If the item group is the hovered item - elseif item_group == ItemGroup.Hovered then - -- - - -- If the exit if directory flag is passed, - -- and the hovered item is a directory, - -- then exit the function - if exit_if_dir and hovered_item_is_dir() then return end - - -- Replace the shell variable in the command - -- with the quoted path of the hovered item - command = command:gsub( - shell_variable_pattern, - escape_replacement_string(get_path_of_hovered_item(true)) - ) - - -- Otherwise, exit the function - else - return - end - - -- Merge the command back into the arguments given - args = merge_tables({ command }, args) - - -- Emit the command to operate on the hovered item - ya.manager_emit("shell", args) -end - --- Function to handle the paste command ----@type CommandFunction -local function handle_paste(args, config) - -- - - -- If the hovered item is not a directory or smart paste is not wanted - if - not hovered_item_is_dir() - or not (config.smart_paste or table_pop(args, "smart", false)) - then - -- - - -- Just paste the items inside the current directory - -- and exit the function - return ya.manager_emit("paste", args) - end - - -- Otherwise, enter the directory - ya.manager_emit("enter", {}) - - -- Paste the items inside the directory - ya.manager_emit("paste", args) - - -- Leave the directory - ya.manager_emit("leave", {}) -end - --- Function to execute the tab create command ----@param state State The state object ----@param args Arguments The arguments passed to the plugin ----@return nil -local execute_tab_create = ya.sync(function(state, args) - -- - - -- Get the hovered item - local hovered_item = cx.active.current.hovered - - -- If the hovered item is nil, - -- or if the hovered item is not a directory, - -- or if the user doesn't want to smartly - -- create a tab in the hovered directory - if - not hovered_item - or not hovered_item.cha.is_dir - or not ( - state.config.smart_tab_create - or table_pop(args, "smart", false) - ) - then - -- - - -- Emit the command to create a new tab with the arguments - -- and exit the function - return ya.manager_emit("tab_create", args) - end - - -- Otherwise, emit the command to create a new tab - -- with the hovered item's url - ya.manager_emit("tab_create", { hovered_item.url }) -end) - --- Function to handle the tab create command ----@type CommandFunction -local function handle_tab_create(args) - -- - - -- Call the function to execute the tab create command - execute_tab_create(args) -end - --- Function to execute the tab switch command ----@param state State The state object ----@param args Arguments The arguments passed to the plugin ----@return nil -local execute_tab_switch = ya.sync(function(state, args) - -- - - -- Get the tab index - local tab_index = args[1] - - -- If no tab index is given, exit the function - if not tab_index then return end - - -- If the user doesn't want to create tabs - -- when switching to a new tab, - -- or the tab index is not given, - -- then just call the tab switch command - -- and exit the function - if - not (state.config.smart_tab_switch or table_pop(args, "smart", false)) - then - return ya.manager_emit("tab_switch", args) - end - - -- Get the number of tabs currently open - local number_of_open_tabs = #cx.tabs - - -- Iterate from the number of current open tabs - -- to the given tab number - for _ = number_of_open_tabs, tab_index do - -- - - -- Call the tab create command - ya.manager_emit("tab_create", { current = true }) - end - - -- Switch to the given tab index - ya.manager_emit("tab_switch", args) -end) - --- Function to handle the tab switch command ----@type CommandFunction -local function handle_tab_switch(args) - -- - - -- Call the function to execute the tab switch command - execute_tab_switch(args) -end - --- Function to do the wraparound for the arrow command ----@param _ any ----@param args Arguments The arguments passed to the plugin ----@return nil -local wraparound_arrow = ya.sync(function(_, args) - -- - - -- Get the current tab - local current_tab = cx.active.current - - -- Get the step from the arguments given - local step = table.remove(args, 1) - - -- Get the number of files in the current tab - local number_of_files = #current_tab.files - - -- If there are no files in the current tab, exit the function - if number_of_files == 0 then return end - - -- Get the new cursor index, - -- which is the current cursor position plus the step given - -- to the arrow function, modulus the number of files in - -- the current tab - local new_cursor_index = (current_tab.cursor + step) % number_of_files - - -- Emit the arrow function with the new cursor index minus - -- the current cursor index to determine how to move the cursor - ya.manager_emit( - "arrow", - merge_tables(args, { new_cursor_index - current_tab.cursor }) - ) -end) - --- Function to handle the arrow command ----@type CommandFunction -local function handle_arrow(args, config) - -- - - -- If wraparound file navigation isn't wanted, - -- then execute the arrow command - if not config.wraparound_file_navigation then - ya.manager_emit("arrow", args) - - -- Otherwise, call the wraparound arrow function - else - wraparound_arrow(args) - end -end - --- Function to get the directory items in the parent directory ----@param _ any ----@param directories_only boolean Whether to only get directories ----@return string[] directory_items The list of paths to the directory items -local get_parent_directory_items = ya.sync(function(_, directories_only) - -- - - -- Initialise the list of directory items - local directory_items = {} - - -- Get the parent directory - local parent_directory = cx.active.parent - - -- If the parent directory doesn't exist, - -- return the empty list of directory items - if not parent_directory then return directory_items end - - -- Otherwise, iterate over the items in the parent directory - for _, item in ipairs(parent_directory.files) do - -- - - -- If the directories only flag is passed, - -- and the item is not a directory, - -- then skip the item - if directories_only and not item.cha.is_dir then goto continue end - - -- Otherwise, add the item to the list of directory items - table.insert(directory_items, item) - - -- The continue label to skip the item - ::continue:: - end - - -- Return the list of directory items - return directory_items -end) - --- Function to execute the parent arrow command ----@param state State The state object ----@param args Arguments The arguments passed to the plugin ----@return nil -local execute_parent_arrow = ya.sync(function(state, args) - -- - - -- Gets the parent directory - local parent_directory = cx.active.parent - - -- If the parent directory doesn't exist, - -- then exit the function - if not parent_directory then return end - - -- Get the offset from the arguments given - local offset = table.remove(args, 1) - - -- Get the type of the offset - local offset_type = type(offset) - - -- If the offset is not a number, - -- then show an error that the offset is not a number - -- and exit the function - if offset_type ~= "number" then - return show_error( - string.format( - "The given offset is not of the type 'number', " - .. "instead it is a '%s', " - .. "with value '%s'", - offset_type, - tostring(offset) - ) - ) - end - - -- Get the number of items in the parent directory - local number_of_items = #parent_directory.files - - -- Initialise the new cursor index - -- to the current cursor index - local new_cursor_index = parent_directory.cursor - - -- Get whether the user wants to sort directories first - local sort_directories_first = cx.active.pref.sort_dir_first - - -- If wraparound file navigation is wanted - if state.config.wraparound_file_navigation then - -- - - -- If the user sorts their directories first - if sort_directories_first then - -- - - -- Get the directories in the parent directory - local directories = get_parent_directory_items(true) - - -- Get the number of directories in the parent directory - local number_of_directories = #directories - - -- If the number of directories is 0, then exit the function - if number_of_directories == 0 then return end - - -- Get the new cursor index by adding the offset, - -- and modding the whole thing by the number of directories - new_cursor_index = (parent_directory.cursor + offset) - % number_of_directories - - -- Otherwise, if the user doesn't sort their directories first - else - -- - - -- Get the new cursor index by adding the offset, - -- and modding the whole thing by the number of - -- items in the parent directory - new_cursor_index = (parent_directory.cursor + offset) - % number_of_items - end - - -- Otherwise, get the new cursor index normally - -- by adding the offset to the cursor index - else - new_cursor_index = parent_directory.cursor + offset - end - - -- Increment the cursor index by 1. - -- The cursor index needs to be increased by 1 - -- as the cursor index is 0-based, while Lua - -- tables are 1-based. - new_cursor_index = new_cursor_index + 1 - - -- Get the starting index of the loop - local start_index = new_cursor_index - - -- Get the ending index of the loop. - -- - -- If the offset given is negative, set the end index to 1, - -- as the loop will iterate backwards. - -- Otherwise, if the step given is positive, - -- set the end index to the number of items in the - -- parent directory. - local end_index = offset < 0 and 1 or number_of_items - - -- Get the step for the loop. - -- - -- If the offset given is negative, set the step to -1, - -- as the loop will iterate backwards. - -- Otherwise, if the step given is positive, set - -- the step to 1 to iterate forwards. - local step = offset < 0 and -1 or 1 - - -- Iterate over the parent directory items - for i = start_index, end_index, step do - -- - - -- Get the directory item - local directory_item = parent_directory.files[i] - - -- If the directory item exists and is a directory - if directory_item and directory_item.cha.is_dir then - -- - - -- Emit the command to change directory to - -- the directory item and exit the function - return ya.manager_emit("cd", { directory_item.url }) - end - end -end) - --- Function to handle the parent arrow command ----@type CommandFunction -local function handle_parent_arrow(args) - -- - - -- Call the function to execute the parent arrow command - -- with the arguments given - execute_parent_arrow(args) -end - --- Function to handle the editor command ----@type CommandFunction -local function handle_editor(args, config, command_table) - -- - - -- Get the editor environment variable - local editor = os.getenv("EDITOR") - - -- If the editor not set, exit the function - if not editor then return end - - -- Call the handle shell function - -- with the editor command - handle_shell( - merge_tables({ - editor .. " $@", - block = true, - exit_if_dir = true, - }, args), - config, - command_table - ) -end - --- Function to handle the pager command ----@type CommandFunction -local function handle_pager(args, config, command_table) - -- - - -- Get the pager environment variable - local pager = os.getenv("PAGER") - - -- If the pager is not set, exit the function - if not pager then return end - - -- Call the handle shell function - -- with the pager command - handle_shell( - merge_tables({ - pager .. " $@", - block = true, - exit_if_dir = true, - }, args), - config, - command_table - ) -end - --- Function to run the commands given ----@param command string The command passed to the plugin ----@param args Arguments The arguments passed to the plugin ----@param config Configuration The configuration object ----@return nil -local function run_command_func(command, args, config) - -- - - -- The command table - ---@type CommandTable - local command_table = { - [Commands.Open] = handle_open, - [Commands.Enter] = handle_enter, - [Commands.Leave] = handle_leave, - [Commands.Rename] = function(_) handle_yazi_command("rename", args) end, - [Commands.Remove] = function(_) handle_yazi_command("remove", args) end, - [Commands.Create] = handle_create, - [Commands.Shell] = handle_shell, - [Commands.Paste] = handle_paste, - [Commands.TabCreate] = handle_tab_create, - [Commands.TabSwitch] = handle_tab_switch, - [Commands.Arrow] = handle_arrow, - [Commands.ParentArrow] = handle_parent_arrow, - [Commands.Editor] = handle_editor, - [Commands.Pager] = handle_pager, - } - - -- Get the function for the command - ---@type CommandFunction|nil - local command_func = command_table[command] - - -- If the function isn't found, notify the user and exit the function - if not command_func then - return show_error("Unknown command: " .. command) - end - - -- Otherwise, call the function for the command - command_func(args, config, command_table) -end - --- The setup function to setup the plugin ----@param _ any ----@param opts Configuration|nil The options given to the plugin ----@return nil -local function setup(_, opts) - -- - - -- Initialise the plugin - initialise_plugin(opts) -end - --- Function to be called to use the plugin ----@param _ any ----@param job { args: Arguments } The job object given by Yazi ----@return nil -local function entry(_, job) - -- - - -- Get the arguments to the plugin - ---@type Arguments - local args = parse_number_arguments(job.args) - - -- Get the command passed to the plugin - local command = table.remove(args, 1) - - -- If the command isn't given, exit the function - if not command then return end - - -- Get the configuration object - local config = get_config() - - -- If the configuration hasn't been initialised yet, - -- then initialise the plugin with the default configuration, - -- as it hasn't been initialised either - if not config then config = initialise_plugin() end - - -- Call the function to handle the commands - run_command_func(command, args, config) -end - --- Returns the table required for Yazi to run the plugin ----@return { setup: fun(): nil, entry: fun(): nil } -return { - setup = setup, - entry = entry, -} diff --git a/config/yazi/plugins/augment-command.yazi/main.lua b/config/yazi/plugins/augment-command.yazi/main.lua index 4fb07e3b..53c3d7c0 100644 --- a/config/yazi/plugins/augment-command.yazi/main.lua +++ b/config/yazi/plugins/augment-command.yazi/main.lua @@ -76,6 +76,7 @@ ---@field archive_path string|nil The path to the archive ---@field destination_path string|nil The path to the destination ---@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 ---@type string @@ -2137,16 +2138,6 @@ local function recursively_extract_archive( local get_extractor_result, extractor = 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 not extractor then return merge_tables(get_extractor_result, { @@ -2155,6 +2146,21 @@ local function recursively_extract_archive( }) 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, -- the error message and the password local archive_files, archive_directories, error = extractor:get_items() @@ -2171,7 +2177,7 @@ local function recursively_extract_archive( } -- Return the extraction result - return add_paths_to_result(extraction_result) + return add_additional_info(extraction_result) end -- 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 not extraction_result.successful then - return add_paths_to_result(extraction_result) + return add_additional_info(extraction_result) end -- Get the result of moving the extracted items @@ -2202,7 +2208,7 @@ local function recursively_extract_archive( or not extracted_items_path or not config.recursively_extract_archives then - return add_paths_to_result(move_result) + return add_additional_info(move_result) end -- Get the url of the extracted items path @@ -2272,20 +2278,38 @@ local function recursively_extract_archive( end -- Return the move result - return add_paths_to_result(move_result) + return add_additional_info(move_result) end -- Function to show an extraction error ---@param extraction_result ExtractionResult The extraction result ---@return nil 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({ string.format( "Failed to extract archive at: %s", extraction_result.archive_path ), string.format("Destination: %s", extraction_result.destination_path), - string.format("Error: %s", extraction_result.error), + error_line, }, "\n")) end diff --git a/config/yazi/plugins/chmod.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/chmod.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/chmod.yazi/init.lua b/config/yazi/plugins/chmod.yazi/init.lua deleted file mode 100644 index 183c31e2..00000000 --- a/config/yazi/plugins/chmod.yazi/init.lua +++ /dev/null @@ -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, -} diff --git a/config/yazi/plugins/exifaudio.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/exifaudio.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/exifaudio.yazi/init.lua b/config/yazi/plugins/exifaudio.yazi/init.lua deleted file mode 100644 index 4e6980bd..00000000 --- a/config/yazi/plugins/exifaudio.yazi/init.lua +++ /dev/null @@ -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 diff --git a/config/yazi/plugins/full-border.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/full-border.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/full-border.yazi/init.lua b/config/yazi/plugins/full-border.yazi/init.lua deleted file mode 100644 index 233ebf0f..00000000 --- a/config/yazi/plugins/full-border.yazi/init.lua +++ /dev/null @@ -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 } diff --git a/config/yazi/plugins/git.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/git.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/git.yazi/init.lua b/config/yazi/plugins/git.yazi/init.lua deleted file mode 100644 index 64c3e8eb..00000000 --- a/config/yazi/plugins/git.yazi/init.lua +++ /dev/null @@ -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 } diff --git a/config/yazi/plugins/glow.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/glow.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/glow.yazi/init.lua b/config/yazi/plugins/glow.yazi/init.lua deleted file mode 100644 index cc813e9d..00000000 --- a/config/yazi/plugins/glow.yazi/init.lua +++ /dev/null @@ -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 diff --git a/config/yazi/plugins/hexyl.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/hexyl.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/hexyl.yazi/init.lua b/config/yazi/plugins/hexyl.yazi/init.lua deleted file mode 100644 index 6fe0990e..00000000 --- a/config/yazi/plugins/hexyl.yazi/init.lua +++ /dev/null @@ -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 diff --git a/config/yazi/plugins/hide-preview.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/hide-preview.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/hide-preview.yazi/init.lua b/config/yazi/plugins/hide-preview.yazi/init.lua deleted file mode 100644 index fcf4094f..00000000 --- a/config/yazi/plugins/hide-preview.yazi/init.lua +++ /dev/null @@ -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 } diff --git a/config/yazi/plugins/max-preview.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/max-preview.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/max-preview.yazi/init.lua b/config/yazi/plugins/max-preview.yazi/init.lua deleted file mode 100644 index 64edfed6..00000000 --- a/config/yazi/plugins/max-preview.yazi/init.lua +++ /dev/null @@ -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 } diff --git a/config/yazi/plugins/mediainfo.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/mediainfo.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/mediainfo.yazi/LICENSE b/config/yazi/plugins/mediainfo.yazi/LICENSE deleted file mode 100644 index 0399f1c2..00000000 --- a/config/yazi/plugins/mediainfo.yazi/LICENSE +++ /dev/null @@ -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. diff --git a/config/yazi/plugins/mediainfo.yazi/README.md b/config/yazi/plugins/mediainfo.yazi/README.md deleted file mode 100644 index 4958f86f..00000000 --- a/config/yazi/plugins/mediainfo.yazi/README.md +++ /dev/null @@ -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"}, -] -``` diff --git a/config/yazi/plugins/mediainfo.yazi/init.lua b/config/yazi/plugins/mediainfo.yazi/init.lua deleted file mode 100644 index aa74285c..00000000 --- a/config/yazi/plugins/mediainfo.yazi/init.lua +++ /dev/null @@ -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 diff --git a/config/yazi/plugins/miller.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/miller.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/miller.yazi/init.lua b/config/yazi/plugins/miller.yazi/init.lua deleted file mode 100644 index 9a4a7528..00000000 --- a/config/yazi/plugins/miller.yazi/init.lua +++ /dev/null @@ -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 diff --git a/config/yazi/plugins/nbpreview.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/nbpreview.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/nbpreview.yazi/init.lua b/config/yazi/plugins/nbpreview.yazi/init.lua deleted file mode 100644 index 2a41cc35..00000000 --- a/config/yazi/plugins/nbpreview.yazi/init.lua +++ /dev/null @@ -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 diff --git a/config/yazi/plugins/ouch.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/ouch.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/ouch.yazi/init.lua b/config/yazi/plugins/ouch.yazi/init.lua deleted file mode 100644 index 6e5c9a60..00000000 --- a/config/yazi/plugins/ouch.yazi/init.lua +++ /dev/null @@ -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 diff --git a/config/yazi/plugins/relative-motions.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/relative-motions.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/relative-motions.yazi/init.lua b/config/yazi/plugins/relative-motions.yazi/init.lua deleted file mode 100644 index be938098..00000000 --- a/config/yazi/plugins/relative-motions.yazi/init.lua +++ /dev/null @@ -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 = "" }, { on = "" } -} - --- 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 = "" }, { on = "" }, - -- 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 == "" then - return "j" - elseif dir == "" 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, -} diff --git a/config/yazi/plugins/rose-pine.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/rose-pine.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/starship.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/starship.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/starship.yazi/LICENSE b/config/yazi/plugins/starship.yazi/LICENSE deleted file mode 100644 index c03ce66d..00000000 --- a/config/yazi/plugins/starship.yazi/LICENSE +++ /dev/null @@ -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. diff --git a/config/yazi/plugins/starship.yazi/README.md b/config/yazi/plugins/starship.yazi/README.md deleted file mode 100644 index a6fcab43..00000000 --- a/config/yazi/plugins/starship.yazi/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# starship.yazi - -Starship prompt plugin for [Yazi](https://github.com/sxyazi/yazi) - - - -## 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`: - -
-Click to expand - -```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 -``` - -
- -> [!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) diff --git a/config/yazi/plugins/starship.yazi/init.lua b/config/yazi/plugins/starship.yazi/init.lua deleted file mode 100644 index b8672c54..00000000 --- a/config/yazi/plugins/starship.yazi/init.lua +++ /dev/null @@ -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, -} diff --git a/config/yazi/plugins/torrent-preview.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/torrent-preview.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/torrent-preview.yazi/init.lua b/config/yazi/plugins/torrent-preview.yazi/init.lua deleted file mode 100644 index d692ce33..00000000 --- a/config/yazi/plugins/torrent-preview.yazi/init.lua +++ /dev/null @@ -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 - diff --git a/config/yazi/plugins/what-size.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/what-size.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/what-size.yazi/init.lua b/config/yazi/plugins/what-size.yazi/init.lua deleted file mode 100644 index e84e1c12..00000000 --- a/config/yazi/plugins/what-size.yazi/init.lua +++ /dev/null @@ -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, -} diff --git a/config/yazi/plugins/yatline.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/config/yazi/plugins/yatline.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY deleted file mode 100644 index e69de29b..00000000 diff --git a/config/yazi/plugins/yatline.yazi/init.lua b/config/yazi/plugins/yatline.yazi/init.lua deleted file mode 100644 index a1e35c31..00000000 --- a/config/yazi/plugins/yatline.yazi/init.lua +++ /dev/null @@ -1,1072 +0,0 @@ ---- @diagnostic disable: undefined-global, undefined-field ---- @alias Mode Mode Comes from Yazi. ---- @alias Rect Rect Comes from Yazi. ---- @alias Paragraph Paragraph Comes from Yazi. ---- @alias Line Line Comes from Yazi. ---- @alias Span Span Comes from Yazi. ---- @alias Color Color Comes from Yazi. ---- @alias Config Config The config used for setup. ---- @alias Coloreds Coloreds The array returned by colorizer in {{string, Color}, {string, Color} ... } format ---- @alias Side # [ LEFT ... RIGHT ] ---- | `enums.LEFT` # The left side of either the header-line or status-line. [ LEFT ... ] ---- | `enums.RIGHT` # The right side of either the header-line or status-line. [ ... RIGHT] ---- @alias SeparatorType ---- | `enums.OUTER` # Separators on the outer side of sections. [ c o | c o | c o ... ] or [ ... o c | o c | o c ] ---- | `enums.INNER` # Separators on the inner side of sections. [ c i c | c i c | c i c ... ] or [ ... c i c | c i c | c i c ] ---- @alias ComponentType ---- | `enums.A` # Components on the first section. [ A | | ... ] or [ ... | | A ] ---- | `enums.B` # Components on the second section. [ | B | ... ] or [ ... | B | ] ---- | `enums.C` # Components on the third section. [ | | C ... ] or [ ... C | | ] - ---==================-- --- Type Declaration -- ---==================-- - -Yatline = {} - -local Side = { LEFT = 0, RIGHT = 1 } -local SeparatorType = { OUTER = 0, INNER = 1 } -local ComponentType = { A = 0, B = 1, C = 2 } - ---=========================-- --- Variable Initialization -- ---=========================-- - -local section_separator_open -local section_separator_close - -local inverse_separator_open -local inverse_separator_close - -local part_separator_open -local part_separator_close - -local separator_style = { bg = nil, fg = nil } - -local style_a -local style_b -local style_c - -local style_a_normal_bg -local style_a_select_bg -local style_a_un_set_bg - -local permissions_t_fg -local permissions_r_fg -local permissions_w_fg -local permissions_x_fg -local permissions_s_fg - -local tab_width - -local selected_icon -local copied_icon -local cut_icon - -local selected_fg -local copied_fg -local cut_fg - -local task_total_icon -local task_succ_icon -local task_fail_icon -local task_found_icon -local task_processed_icon - -local task_total_fg -local task_succ_fg -local task_fail_fg -local task_found_fg -local task_processed_fg - -local show_background - -local section_order = { "section_a", "section_b", "section_c" } - ---=================-- --- Component Setup -- ---=================-- - ---- Sets the background of style_a according to the tab's mode. ---- @param mode Mode The mode of the active tab. ---- @see cx.active.mode To get the active tab's mode. -local function set_mode_style(mode) - if mode.is_select then - style_a.bg = style_a_select_bg - elseif mode.is_unset then - style_a.bg = style_a_un_set_bg - else - style_a.bg = style_a_normal_bg - end -end - ---- Sets the style of the component according to the its type. ---- @param component Span Component that will be styled. ---- @param component_type ComponentType Which section component will be in [ a | b | c ]. ---- @see Style To see how to style, in Yazi's documentation. -local function set_component_style(component, component_type) - if component_type == ComponentType.A then - component:style(style_a):bold() - elseif component_type == ComponentType.B then - component:style(style_b) - else - component:style(style_c) - end -end - ---- Connects component to a separator. ---- @param component Span Component that will be connected to separator. ---- @param side Side Left or right side of the either header-line or status-line. ---- @param separator_type SeparatorType Where will there be a separator in the section. ---- @return Line line A Line which has component and separator. -local function connect_separator(component, side, separator_type) - local open, close - if separator_type == SeparatorType.OUTER and not (separator_style.bg == "reset" and separator_style.fg == "reset") then - open = ui.Span(section_separator_open) - close = ui.Span(section_separator_close) - - if separator_style.fg == "reset" then - if separator_style.bg ~= "reset" and separator_style.bg ~= nil then - open = ui.Span(inverse_separator_open) - close = ui.Span(inverse_separator_close) - - separator_style.fg, separator_style.bg = separator_style.bg, separator_style.fg - else - return ui.Line { component } - end - end - else - open = ui.Span(part_separator_open) - close = ui.Span(part_separator_close) - end - - open:style(separator_style) - close:style(separator_style) - - if side == Side.LEFT then - return ui.Line { component, close } - else - return ui.Line { open, component } - end -end - ---==================-- --- Helper Functions -- ---==================-- - ---- Gets the file name from given file extension. ---- @param file_name string The name of a file whose extension will be taken. ---- @return string file_extension Extension of a file. -local function get_file_extension(file_name) - local extension = file_name:match("^.+%.(.+)$") - - if extension == nil or extension == "" then - return "null" - else - return extension - end -end - ---- Reverse the order of given array ---- @param array Line Array which wants to be reversed. ---- @return table reversed Reversed ordered given array. -local function reverse_order(array) - local reversed = {} - for i = #array, 1, -1 do - table.insert(reversed, array[i]) - end - - return reversed -end - ---========================-- --- Component String Group -- ---========================-- - -Yatline.string = {} -Yatline.string.get = {} -Yatline.string.has_separator = true - ---- Creates a component from given string according to other parameters. ---- @param string string The text which will be shown inside of the component. ---- @param component_type ComponentType Which section component will be in [ a | b | c ]. ---- @return Line line Customized Line which follows desired style of the parameters. ---- @see set_mode_style To know how mode style selected. ---- @see set_component_style To know how component style applied. -function Yatline.string.create(string, component_type) - local span = ui.Span(" " .. string .. " ") - set_mode_style(cx.active.mode) - set_component_style(span, component_type) - - return ui.Line{span} -end - ---- Gets the hovered file's name of the current active tab. ---- @return string name Current active tab's hovered file's name. -function Yatline.string.get:hovered_name() - local hovered = cx.active.current.hovered - if hovered then - return hovered.name - else - return "" - end -end - ---- Gets the hovered file's path of the current active tab. ---- @return string path Current active tab's hovered file's path. -function Yatline.string.get:hovered_path() - local hovered = cx.active.current.hovered - if hovered then - return ya.readable_path(tostring(hovered.url)) - else - return "" - end -end - ---- Gets the hovered file's size of the current active tab. ---- @return string size Current active tab's hovered file's size. -function Yatline.string.get:hovered_size() - local hovered = cx.active.current.hovered - if hovered then - return ya.readable_size(hovered:size() or hovered.cha.len) - else - return "" - end -end - ---- Gets the hovered file's path of the current active tab. ---- @return string mime Current active tab's hovered file's mime. -function Yatline.string.get:hovered_mime() - local hovered = cx.active.current.hovered - if hovered then - return hovered:mime() - else - return "" - end -end - ---- Gets the hovered file's user and group ownership of the current active tab. ---- @return string ownership Current active tab's hovered file's user and group ownership. -function Yatline.string.get:hovered_ownership() - local hovered = cx.active.current.hovered - - if hovered then - return ya.user_name(hovered.cha.uid) .. ":" .. ya.group_name(hovered.cha.gid) - else - return "" - end -end - ---- Gets the hovered file's extension of the current active tab. ---- @param show_icon boolean Whether or not an icon will be shown. ---- @return string file_extension Current active tab's hovered file's extension. -function Yatline.string.get:hovered_file_extension(show_icon) - local hovered = cx.active.current.hovered - - if hovered then - local cha = hovered.cha - - local name - if cha.is_dir then - name = "dir" - else - name = get_file_extension(hovered.url:name()) - end - - if show_icon then - local icon = hovered:icon().text - return icon .. " " .. name - else - return name - end - else - return "" - end -end - ---- Gets the path of the current active tab. ---- @return string path Current active tab's path. -function Yatline.string.get:tab_path() - local cwd = cx.active.current.cwd - local filter = cx.active.current.files.filter - - local search = cwd.is_search and string.format(" (search: %s", cwd:frag()) or "" - - local suffix - if not filter then - suffix = search == "" and search or search .. ")" - elseif search == "" then - suffix = string.format(" (filter: %s)", tostring(filter)) - else - suffix = string.format("%s, filter: %s)", search, tostring(filter)) - end - - return ya.readable_path(tostring(cx.active.current.cwd)) .. suffix -end - ---- Gets the mode of active tab. ---- @return string mode Active tab's mode. -function Yatline.string.get:tab_mode() - local mode = tostring(cx.active.mode):upper() - if mode == "UNSET" then - mode = "UN-SET" - end - - return mode -end - ---- Gets the number of files in the current active tab. ---- @return string num_files Number of files in the current active tab. -function Yatline.string.get:tab_num_files() - return tostring(#cx.active.current.files) -end - ---- Gets the cursor position in the current active tab. ---- @return string cursor_position Current active tab's cursor position. -function Yatline.string.get:cursor_position() - local cursor = cx.active.current.cursor - local length = #cx.active.current.files - - if length ~= 0 then - return string.format(" %2d/%-2d", cursor + 1, length) - else - return "0" - end -end - ---- Gets the cursor position as percentage which is according to the number of files inside of current active tab. ---- @return string percentage Percentage of current active tab's cursor position and number of percentages. -function Yatline.string.get:cursor_percentage() - local percentage = 0 - local cursor = cx.active.current.cursor - local length = #cx.active.current.files - if cursor ~= 0 and length ~= 0 then - percentage = math.floor((cursor + 1) * 100 / length) - end - - if percentage == 0 then - return " Top " - elseif percentage == 100 then - return " Bot " - else - return string.format("%3d%% ", percentage) - end -end - ---- Gets the local date or time values. ---- @param format string Format for giving desired date or time values. ---- @return string date Date or time values. ---- @see os.date To see how format works. -function Yatline.string.get:date(format) - return tostring(os.date(format)) -end - ---======================-- --- Component Line Group -- ---======================-- - -Yatline.line = {} -Yatline.line.get = {} -Yatline.line.has_separator = false - ---- To follow component group naming and functions, returns the given line without any changes. ---- @param line Line The line already pre-defined. ---- @param component_type ComponentType Which section component will be in [ a | b | c ]. Will not be used. ---- @return Line line The given line as an input. -function Yatline.line.create(line, component_type) - return line -end - ---- Creates and returns line component for tabs. ---- @param side Side Left or right side of the either header-line or status-line. ---- @return Line line Customized Line which contains tabs. ---- @see set_mode_style To know how mode style selected. ---- @see set_component_style To know how component style applied. ---- @see connect_separator To know how component and separator connected. -function Yatline.line.get:tabs(side) - local tabs = #cx.tabs - local lines = {} - - local in_side - if side == "left" then - in_side = Side.LEFT - else - in_side = Side.RIGHT - end - - for i = 1, tabs do - local text = i - if tab_width > 2 then - text = ya.truncate(text .. " " .. cx.tabs[i]:name(), { max = tab_width }) - end - - separator_style = { bg = nil, fg = nil } - if i == cx.tabs.idx then - local span = ui.Span(" " .. text .. " ") - set_mode_style(cx.tabs[i].mode) - set_component_style(span, ComponentType.A) - - if style_a.bg ~= "reset" or show_background then - separator_style.fg = style_a.bg - if show_background then - separator_style.bg = style_c.bg - end - - lines[#lines + 1] = connect_separator(span, in_side, SeparatorType.OUTER) - else - separator_style.fg = style_a.fg - - lines[#lines + 1] = connect_separator(span, in_side, SeparatorType.INNER) - end - else - local span = ui.Span(" " .. text .. " ") - if show_background then - set_component_style(span, ComponentType.C) - else - span:style({ fg = style_c.fg }) - end - - if i == cx.tabs.idx - 1 then - set_mode_style(cx.tabs[i + 1].mode) - - local open, close - if style_a.bg ~= "reset" or ( show_background and style_c.bg ~= "reset" ) then - if not show_background or ( show_background and style_c.bg == "reset" ) then - separator_style.fg = style_a.bg - if show_background then - separator_style.bg = style_c.bg - end - - open = ui.Span(inverse_separator_open) - close = ui.Span(inverse_separator_close) - else - separator_style.bg = style_a.bg - if show_background then - separator_style.fg = style_c.bg - end - - open = ui.Span(section_separator_open) - close = ui.Span(section_separator_close) - end - else - separator_style.fg = style_c.fg - - open = ui.Span(part_separator_open) - close = ui.Span(part_separator_close) - end - - open:style(separator_style) - close:style(separator_style) - - if in_side == Side.LEFT then - lines[#lines + 1] = ui.Line { span, close } - else - lines[#lines + 1] = ui.Line { open, span } - end - else - separator_style.fg = style_c.fg - if show_background then - separator_style.bg = style_c.bg - end - - lines[#lines + 1] = connect_separator(span, in_side, SeparatorType.INNER) - end - end - end - - if in_side == Side.RIGHT then - local lines_in_right = {} - for i = #lines, 1, -1 do - lines_in_right[#lines_in_right + 1] = lines[i] - end - - return ui.Line(lines_in_right) - else - return ui.Line(lines) - end -end - ---==========================-- --- Component Coloreds Group -- ---==========================-- - -Yatline.coloreds = {} -Yatline.coloreds.get = {} -Yatline.coloreds.has_separator = true - ---- Creates a component from given Coloreds according to other parameters. ---- The component it created, can contain multiple strings with different foreground color. ---- @param coloreds Coloreds The array which contains an array which contains text which will be shown inside of the component and its foreground color. ---- @param component_type ComponentType Which section component will be in [ a | b | c ]. ---- @return Line line Customized Line which follows desired style of the parameters. ---- @see set_mode_style To know how mode style selected. ---- @see set_component_style To know how component style applied. -function Yatline.coloreds.create(coloreds, component_type) - set_mode_style(cx.active.mode) - - local spans = {} - for i, colored in ipairs(coloreds) do - local span = ui.Span(colored[1]) - set_component_style(span, component_type) - span:fg(colored[2]) - - spans[i] = span - end - - return ui.Line(spans) -end - ---- Gets the hovered file's permissions of the current active tab. ---- @return Coloreds coloreds Current active tab's hovered file's permissions -function Yatline.coloreds.get:permissions() - local hovered = cx.active.current.hovered - - if hovered then - local perm = hovered.cha:perm() - - if perm then - local coloreds = {} - coloreds[1] = { " ", "black" } - - for i = 1, #perm do - local c = perm:sub(i, i) - - local fg = permissions_t_fg - if c == "-" then - fg = permissions_s_fg - elseif c == "r" then - fg = permissions_r_fg - elseif c == "w" then - fg = permissions_w_fg - elseif c == "x" or c == "s" or c == "S" or c == "t" or c == "T" then - fg = permissions_x_fg - end - - coloreds[i + 1] = { c, fg } - end - - coloreds[#perm + 2] = { " ", "black" } - - return coloreds - else - return "" - end - else - return "" - end -end - ---- Gets the number of selected and yanked files of the active tab. ---- @return Coloreds coloreds Active tab's number of selected and yanked files. -function Yatline.coloreds.get:count() - local num_yanked = #cx.yanked - local num_selected = #cx.active.selected - - local yanked_fg, yanked_icon - if cx.yanked.is_cut then - yanked_fg = cut_fg - yanked_icon = cut_icon - else - yanked_fg = copied_fg - yanked_icon = copied_icon - end - - local coloreds = { - { string.format(" %s %d ", selected_icon, num_selected), selected_fg }, - { string.format(" %s %d ", yanked_icon, num_yanked), yanked_fg } - } - - return coloreds -end - ---- Gets the number of task states. ---- @return Coloreds coloreds Number of task states. -function Yatline.coloreds.get:task_states() - local tasks = cx.tasks.progress - - local coloreds = { - { string.format(" %s %d ", task_total_icon, tasks.total), task_total_fg }, - { string.format(" %s %d ", task_succ_icon, tasks.succ), task_succ_fg }, - { string.format(" %s %d ", task_fail_icon, tasks.fail), task_fail_fg } - } - - return coloreds -end - ---- Gets the number of task workloads. ---- @return Coloreds coloreds Number of task workloads. -function Yatline.coloreds.get:task_workload() - local tasks = cx.tasks.progress - - local coloreds = { - { string.format(" %s %d ", task_found_icon, tasks.found), task_found_fg }, - { string.format(" %s %d ", task_processed_icon, tasks.processed), task_processed_fg }, - } - - return coloreds -end - ---- Gets colored which contains string based component's string and desired foreground color. ---- @param component_name string String based component's name. ---- @param fg Color Desired foreground color. ---- @param params? table Array of parameters of string based component. It is optional. ---- @return Coloreds coloreds Array of solely array of string based component's string and desired foreground color. -function Yatline.coloreds.get:string_based_component(component_name, fg, params) - local getter = Yatline.string.get[component_name] - - if getter then - local output - if params then - output = getter(Yatline.string.get, table.unpack(params)) - else - output = getter() - end - - - if output ~= nil and output ~= "" then - return { { " " .. output .. " ", fg } } - else - return "" - end - else - return "" - end -end - ---===============-- --- Configuration -- ---===============-- - ---- Configure separators if it is need to be added to the components. ---- Connects them with each component. ---- @param section_components table Array of components in one of the sections. ---- @param component_type ComponentType Which section component will be in [ a | b | c ]. ---- @param in_side Side Left or right side of the either header-line or status-line. ---- @param num_section_b_components integer Number of components in section-b. ---- @param num_section_c_components integer Number of components in section-c. ---- @return table section_line_components Array of line components whether or not connected with separators. ---- @see connect_separator To know how component and separator connected. -local function config_components_separators(section_components, component_type, in_side, num_section_b_components, num_section_c_components) - local num_section_components = #section_components - local section_line_components = {} - for i, component in ipairs(section_components) do - if component[2] == true then - separator_style = { bg = nil, fg = nil } - - local separator_type - if i ~= num_section_components then - if component_type == ComponentType.A then - separator_style = style_a - elseif component_type == ComponentType.B then - separator_style = style_b - else - separator_style = style_c - end - - separator_type = SeparatorType.INNER - else - if component_type == ComponentType.A then - separator_style.fg = style_a.bg - elseif component_type == ComponentType.B then - separator_style.fg = style_b.bg - else - separator_style.fg = style_c.bg - end - - if component_type == ComponentType.A and num_section_b_components ~= 0 then - separator_style.bg = style_b.bg - else - if num_section_c_components == 0 or component_type == ComponentType.C then - if show_background then - separator_style.bg = style_c.bg - end - else - separator_style.bg = style_c.bg - end - end - - separator_type = SeparatorType.OUTER - end - - section_line_components[i] = connect_separator(component[1], in_side, separator_type) - else - if in_side == Side.LEFT then - section_line_components[i] = component[1] - else - section_line_components[i] = component[1] - end - end - end - - return section_line_components -end - ---- Leads the given parameters to the other functions. ---- @param section_a_components table Components array whose components are in section-a of either side. ---- @param section_b_components table Components array whose components are in section-b of either side. ---- @param section_c_components table Components array whose components are in section-c of either side. ---- @param in_side Side Left or right side of the either header-line or status-line. ---- @return table section_a_line_components Array of components whose components are connected to separator and are in section-a of either side. ---- @return table section_b_line_components Array of components whose components are connected to separator and are in section-b of either side. ---- @return table section_c_line_components Array of components whose components are connected to separator and are in section-c of either side. ---- @see config_components_separators To know how separators are configured. -local function config_components(section_a_components, section_b_components, section_c_components, in_side) - local num_section_b_components = #section_b_components - local num_section_c_components = #section_c_components - - local section_a_line_components = config_components_separators(section_a_components, ComponentType.A, in_side, num_section_b_components, num_section_c_components) - local section_b_line_components = config_components_separators(section_b_components, ComponentType.B, in_side, num_section_b_components, num_section_c_components) - local section_c_line_components = config_components_separators(section_c_components, ComponentType.C, in_side, num_section_b_components, num_section_c_components) - - return section_a_line_components, section_b_line_components, section_c_line_components -end - ---- Automatically creates and configures either left or right side according to their config. ---- @param side Config Configuration of either left or right side. ---- @return table section_a_components Components array whose components are in section-a of either side. ---- @return table section_b_components Components array whose components are in section-b of either side. ---- @return table section_c_components Components array whose components are in section-c of either side. -local function config_side(side) - local section_a_components = {} - local section_b_components = {} - local section_c_components = {} - - for _, section in ipairs(section_order) do - local components = side[section] - - local in_section, section_components - if section == "section_a" then - in_section = ComponentType.A - section_components = section_a_components - elseif section == "section_b" then - in_section = ComponentType.B - section_components = section_b_components - else - in_section = ComponentType.C - section_components = section_c_components - end - - for _, component in ipairs(components) do - local component_group = Yatline[component.type] - - if component_group then - if component.custom then - section_components[#section_components + 1] = { component_group.create(component.name, in_section), component_group.has_separator } - else - local getter = component_group.get[component.name] - - if getter then - local output - if component.params then - output = getter(component_group.get, table.unpack(component.params)) - else - output = getter() - end - - if output ~= nil and output ~= "" then - section_components[#section_components + 1] = { component_group.create(output, in_section), component_group.has_separator } - end - end - end - end - end - end - - return section_a_components, section_b_components, section_c_components -end - ---- Automatically creates and configures either header-line or status-line. ---- @param side Config Configuration of either left or right side. ---- @return table left_components Components array whose components are in left side of the line. ---- @return table right_components Components array whose components are in right side of the line. ---- @see config_side To know how components are gotten from side's config. ---- @see config_components To know how components are configured. -local function config_line(side, in_side) - local section_a_components, section_b_components, section_c_components = config_side(side) - - local section_a_line_components, section_b_line_components, section_c_line_components = config_components(section_a_components, section_b_components, section_c_components, in_side) - - if in_side == Side.RIGHT then - section_a_line_components = reverse_order(section_a_line_components) - section_b_line_components = reverse_order(section_b_line_components) - section_c_line_components = reverse_order(section_c_line_components) - end - - local section_a_line = ui.Line(section_a_line_components) - local section_b_line = ui.Line(section_b_line_components) - local section_c_line = ui.Line(section_c_line_components) - - if in_side == Side.LEFT then - return ui.Line {section_a_line, section_b_line, section_c_line} - else - return ui.Line {section_c_line, section_b_line, section_a_line} - end -end - ---- Checks if either header-line or status-line contains components. ---- @param line Config Configuration of either header-line or status-line. ---- @return boolean show_line Returns yes if it contains components, otherwise returns no. -local function show_line(line) - local total_components = 0 - - for _, side in pairs(line) do - for _, section in pairs(side) do - total_components = total_components + #section - end - end - - return total_components ~= 0 -end - ---- Creates and configures paragraph which is used as left or right of either ---- header-line or status-line. ---- @param area Rect The area where paragraph will be placed in. ---- @param line? Line The line which used in paragraph. It is optional. ---- @return Paragraph paragraph Configured parapgraph. -local function config_paragraph(area, line) - local line_array = { line } or {} - if show_background then - return ui.Text(line_array):area(area):style(style_c) - else - return ui.Text(line_array):area(area) - end -end - -return { - setup = function(_, config) - config = config or {} - - tab_width = config.tab_width or 20 - - local component_positions = config.component_positions or { "header", "tab", "status" } - - show_background = config.show_background or false - - local display_header_line = config.display_header_line - if display_header_line == nil then - display_header_line = true - end - - local display_status_line = config.display_status_line - if display_status_line == nil then - display_status_line = true - end - - local header_line = config.header_line or { left = { section_a = {}, section_b = {}, section_c = {} }, right = { section_a = {}, section_b = {}, section_c = {} } } - local status_line = config.status_line or { left = { section_a = {}, section_b = {}, section_c = {} }, right = { section_a = {}, section_b = {}, section_c = {} } } - - if config.theme then - config = config.theme - end - - if config.section_separator then - section_separator_open = config.section_separator.open - section_separator_close = config.section_separator.close - else - section_separator_open = "" - section_separator_close = "" - end - - if config.inverse_separator then - inverse_separator_open = config.inverse_separator.open - inverse_separator_close = config.inverse_separator.close - else - inverse_separator_open = "" - inverse_separator_close = "" - end - - if config.part_separator then - part_separator_open = config.part_separator.open - part_separator_close = config.part_separator.close - else - part_separator_open = "" - part_separator_close = "" - end - - if config.style_a then - style_a = { bg = config.style_a.bg_mode.normal, fg = config.style_a.fg } - - style_a_normal_bg = config.style_a.bg_mode.normal - style_a_select_bg = config.style_a.bg_mode.select - style_a_un_set_bg = config.style_a.bg_mode.un_set - else - style_a = { bg = "white", fg = "black" } - - style_a_normal_bg = "white" - style_a_select_bg = "brightyellow" - style_a_un_set_bg = "brightred" - end - - style_b = config.style_b or { bg = "brightblack", fg = "brightwhite" } - style_c = config.style_c or { bg = "black", fg = "brightwhite" } - - permissions_t_fg = config.permissions_t_fg or "green" - permissions_r_fg = config.permissions_r_fg or "yellow" - permissions_w_fg = config.permissions_w_fg or "red" - permissions_x_fg = config.permissions_x_fg or "cyan" - permissions_s_fg = config.permissions_s_fg or "white" - - if config.selected then - selected_fg = config.selected.fg - selected_icon = config.selected.icon - else - selected_fg = "yellow" - selected_icon = "󰻭" - end - - if config.copied then - copied_fg = config.copied.fg - copied_icon = config.copied.icon - else - copied_fg = "green" - copied_icon = "" - end - - if config.cut then - cut_icon = config.cut.icon - cut_fg = config.cut.fg - else - cut_icon = "" - cut_fg = "red" - end - - if config.total then - task_total_icon = config.total.icon - task_total_fg = config.total.fg - else - task_total_icon = "󰮍" - task_total_fg = "yellow" - end - - if config.succ then - task_succ_icon = config.succ.icon - task_succ_fg = config.succ.fg - else - task_succ_icon = "" - task_succ_fg = "green" - end - - if config.fail then - task_fail_icon = config.fail.icon - task_fail_fg = config.fail.fg - else - task_fail_icon = "" - task_fail_fg = "red" - end - - if config.found then - task_found_icon = config.found.icon - task_found_fg = config.found.fg - else - task_found_icon = "󰮕" - task_found_fg = "blue" - end - - if config.processed then - task_processed_icon = config.processed.icon - task_processed_fg = config.processed.fg - else - task_processed_icon = "󰐍" - task_processed_fg = "green" - end - - Progress.partial_render = function(self) - local progress = cx.tasks.progress - if progress.total == 0 then - return { config_paragraph(self._area) } - end - - local gauge = ui.Gauge():area(self._area) - if progress.fail == 0 then - gauge = gauge:gauge_style(THEME.status.progress_normal) - else - gauge = gauge:gauge_style(THEME.status.progress_error) - end - - local percent = 99 - if progress.found ~= 0 then - percent = math.min(99, ya.round(progress.processed * 100 / progress.found)) - end - - local left = progress.total - progress.succ - return { - gauge - :percent(percent) - :label(ui.Span(string.format("%3d%%, %d left", percent, left)):style(THEME.status.progress_label)), - } - end - - if display_header_line then - if show_line(header_line) then - Header.redraw = function(self) - local left_line = config_line(header_line.left, Side.LEFT) - local right_line = config_line(header_line.right, Side.RIGHT) - - return { - config_paragraph(self._area, left_line), - ui.Text(right_line):area(self._area):align(ui.Text.RIGHT) - } - end - - Header.children_add = function() return {} end - Header.children_remove = function() return {} end - end - else - Header.redraw = function() return {} end - end - - if display_status_line then - if show_line(status_line) then - Status.redraw = function(self) - local left_line = config_line(status_line.left, Side.LEFT) - local right_line = config_line(status_line.right, Side.RIGHT) - local right_width = right_line:width() - - return { - config_paragraph(self._area, left_line), - ui.Text(right_line):area(self._area):align(ui.Text.RIGHT), - table.unpack(Progress:new(self._area, right_width):redraw()), - } - end - - Status.children_add = function() return {} end - Status.children_remove = function() return {} end - end - else - Status.redraw = function() return {} end - end - - Root.layout = function(self) - local constraints = {} - for _, component in ipairs(component_positions) do - if (component == "header" and display_header_line) or (component == "status" and display_status_line) then - table.insert(constraints, ui.Constraint.Length(1)) - elseif component == "tab" then - table.insert(constraints, ui.Constraint.Fill(1)) - end - end - - self._chunks = ui.Layout():direction(ui.Layout.VERTICAL):constraints(constraints):split(self._area) - end - - Root.build = function(self) - local childrens = {} - - local i = 1 - for _, component in ipairs(component_positions) do - if component == "header" and display_header_line then - table.insert(childrens, Header:new(self._chunks[i], cx.active)) - i = i + 1 - elseif component == "tab" then - table.insert(childrens, Tab:new(self._chunks[i], cx.active)) - i = i + 1 - elseif component == "status" and display_status_line then - table.insert(childrens, Status:new(self._chunks[i], cx.active)) - i = i + 1 - end - end - - self._children = childrens - end - end, -} diff --git a/config/zsh/.zshrc b/config/zsh/.zshrc index e577990d..85041120 100644 --- a/config/zsh/.zshrc +++ b/config/zsh/.zshrc @@ -143,6 +143,8 @@ bindkey -s '^f' '^utmux neww tmux-sessionizer\n' eval "$(starship init zsh)" eval "$(fzf --zsh)" +eval "$(uv generate-shell-completion zsh)" +eval "$(uvx --generate-shell-completion zsh)" [[ -r ~/.local/share/zsh/plugins/znap/znap.zsh ]] || git clone --depth 1 -- \ diff --git a/dotter b/dotter old mode 100644 new mode 100755 diff --git a/dotter.arm b/dotter.arm old mode 100644 new mode 100755 diff --git a/dotter.exe b/dotter.exe old mode 100644 new mode 100755