From d4bde2f918feefe1cdb3bcd1c2272e92916a9399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Dom=C3=ADnguez?= Date: Thu, 8 May 2025 20:49:10 -0400 Subject: [PATCH 1/8] include flavor.toml --- .config/yazi/flavors/dracula.yazi/flavor.toml | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 .config/yazi/flavors/dracula.yazi/flavor.toml diff --git a/.config/yazi/flavors/dracula.yazi/flavor.toml b/.config/yazi/flavors/dracula.yazi/flavor.toml new file mode 100644 index 0000000..01ded4a --- /dev/null +++ b/.config/yazi/flavors/dracula.yazi/flavor.toml @@ -0,0 +1,170 @@ +# vim:fileencoding=utf-8:foldmethod=marker + +# : Manager {{{ + +[manager] +cwd = { fg = "#8be9fd" } + +# Hovered +hovered = { reversed = true } +preview_hovered = { underline = true } + +# Find +find_keyword = { fg = "#f1fa8c", bold = true, italic = true, underline = true } +find_position = { fg = "#ff79c6", bg = "reset", bold = true, italic = true } + +# Marker +marker_copied = { fg = "#50fa7b", bg = "#50fa7b" } +marker_cut = { fg = "#ff5555", bg = "#ff5555" } +marker_marked = { fg = "#8be9fd", bg = "#8be9fd" } +marker_selected = { fg = "#f1fa8c", bg = "#f1fa8c" } + +# Tab +tab_active = { reversed = true } +tab_inactive = {} +tab_width = 1 + +# Count +count_copied = { fg = "#282a36", bg = "#50fa7b" } +count_cut = { fg = "#282a36", bg = "#ff5555" } +count_selected = { fg = "#282a36", bg = "#f1fa8c" } + +# Border +border_symbol = "│" +border_style = { fg = "#7282b5" } + +# : }}} + + +# : Mode {{{ + +[mode] + +normal_main = { fg = "#282a36", bg = "#bd93f9", bold = true } +normal_alt = { fg = "#bd93f9", bg = "#44475a" } + +# Select mode +select_main = { fg = "#282a36", bg = "#8be9fd", bold = true } +select_alt = { fg = "#8be9fd", bg = "#44475a" } + +# Unset mode +unset_main = { fg = "#282a36", bg = "#ffb86c", bold = true } +unset_alt = { fg = "#ffb86c", bg = "#44475a" } + +# : }}} + + +# : Status bar {{{ + +[status] +# Permissions +perm_sep = { fg = "#7282b5" } +perm_type = { fg = "#bd93f9" } +perm_read = { fg = "#f1fa8c" } +perm_write = { fg = "#ff5555" } +perm_exec = { fg = "#50fa7b" } + +# Progress +progress_label = { fg = "#ffffff", bold = true } +progress_normal = { fg = "#bd93f9", bg = "#63667d" } +progress_error = { fg = "#ff5555", bg = "#63667d" } + +# : }}} + + +# : Pick {{{ + +[pick] +border = { fg = "#bd93f9" } +active = { fg = "#ff79c6", bold = true } +inactive = {} + +# : }}} + + +# : Input {{{ + +[input] +border = { fg = "#bd93f9" } +title = {} +value = {} +selected = { reversed = true } + +# : }}} + + +# : Completion {{{ + +[cmp] +border = { fg = "#bd93f9" } + +# : }}} + + +# : Tasks {{{ + +[tasks] +border = { fg = "#bd93f9" } +title = {} +hovered = { fg = "#ff79c6", underline = true } + +# : }}} + + +# : Which {{{ + +[which] +mask = { bg = "#44475a" } +cand = { fg = "#8be9fd" } +rest = { fg = "#8998c9" } +desc = { fg = "#ff79c6" } +separator = "  " +separator_style = { fg = "#83869c" } + +# : }}} + + +# : Help {{{ + +[help] +on = { fg = "#8be9fd" } +run = { fg = "#ff79c6" } +hovered = { reversed = true, bold = true } +footer = { fg = "#44475a", bg = "#f8f8f2" } + +# : }}} + + +# : Notify {{{ + +[notify] +title_info = { fg = "#50fa7b" } +title_warn = { fg = "#f1fa8c" } +title_error = { fg = "#ff5555" } + +# : }}} + + +# : File-specific styles {{{ + +[filetype] + +rules = [ + # Images + { mime = "image/*", fg = "#8be9fd" }, + + # Media + { mime = "{audio,video}/*", fg = "#f1fa8c" }, + + # Archives + { mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#ff79c6" }, + + # Documents + { mime = "application/{pdf,doc,rtf}", fg = "#50fa7b" }, + + # Fallback + { name = "*", fg = "#f8f8f2" }, + { name = "*/", fg = "#bd93f9" } +] + +# : }}} From 6f7b7da871601e6d19fcc2f8cac062bd8999e9b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Dom=C3=ADnguez?= Date: Sat, 10 May 2025 08:50:54 -0400 Subject: [PATCH 2/8] remove since I no longer use it --- .config/yazi/plugins/git.yazi/LICENSE | 21 --- .config/yazi/plugins/git.yazi/README.md | 78 -------- .config/yazi/plugins/git.yazi/main.lua | 228 ------------------------ 3 files changed, 327 deletions(-) delete mode 100644 .config/yazi/plugins/git.yazi/LICENSE delete mode 100644 .config/yazi/plugins/git.yazi/README.md delete mode 100644 .config/yazi/plugins/git.yazi/main.lua diff --git a/.config/yazi/plugins/git.yazi/LICENSE b/.config/yazi/plugins/git.yazi/LICENSE deleted file mode 100644 index fb5b1d6..0000000 --- a/.config/yazi/plugins/git.yazi/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 yazi-rs - -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/git.yazi/README.md b/.config/yazi/plugins/git.yazi/README.md deleted file mode 100644 index 4c5b07c..0000000 --- a/.config/yazi/plugins/git.yazi/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# git.yazi - -> [!NOTE] -> Yazi v25.2.26 or later is required for this plugin to work. - -Show the status of Git file changes as linemode in the file list. - -https://github.com/user-attachments/assets/34976be9-a871-4ffe-9d5a-c4cdd0bf4576 - -## Installation - -```sh -ya pack -a yazi-rs/plugins:git -``` - -## Setup - -Add the following to your `~/.config/yazi/init.lua`: - -```lua -require("git"):setup() -``` - -And register it as fetchers in your `~/.config/yazi/yazi.toml`: - -```toml -[[plugin.prepend_fetchers]] -id = "git" -name = "*" -run = "git" - -[[plugin.prepend_fetchers]] -id = "git" -name = "*/" -run = "git" -``` - -## Advanced - -You can customize the [Style](https://yazi-rs.github.io/docs/plugins/layout#style) of the status sign with: - -- `th.git.modified` -- `th.git.added` -- `th.git.untracked` -- `th.git.ignored` -- `th.git.deleted` -- `th.git.updated` - -For example: - -```lua --- ~/.config/yazi/init.lua -th.git = th.git or {} -th.git.modified = ui.Style():fg("blue") -th.git.deleted = ui.Style():fg("red"):bold() -``` - -You can also customize the text of the status sign with: - -- `th.git.modified_sign` -- `th.git.added_sign` -- `th.git.untracked_sign` -- `th.git.ignored_sign` -- `th.git.deleted_sign` -- `th.git.updated_sign` - -For example: - -```lua --- ~/.config/yazi/init.lua -th.git = th.git or {} -th.git.modified_sign = "M" -th.git.deleted_sign = "D" -``` - -## License - -This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file. diff --git a/.config/yazi/plugins/git.yazi/main.lua b/.config/yazi/plugins/git.yazi/main.lua deleted file mode 100644 index d8f365a..0000000 --- a/.config/yazi/plugins/git.yazi/main.lua +++ /dev/null @@ -1,228 +0,0 @@ ---- @since 25.4.4 - -local WINDOWS = ya.target_family() == "windows" - --- The code of supported git status, --- also used to determine which status to show for directories when they contain different statuses --- see `bubble_up` -local CODES = { - excluded = 100, -- ignored directory - ignored = 6, -- ignored file - untracked = 5, - modified = 4, - added = 3, - deleted = 2, - updated = 1, - unknown = 0, -} - -local PATTERNS = { - { "!$", CODES.ignored }, - { "?$", CODES.untracked }, - { "[MT]", CODES.modified }, - { "[AC]", CODES.added }, - { "D", CODES.deleted }, - { "U", CODES.updated }, - { "[AD][AD]", CODES.updated }, -} - -local function match(line) - local signs = line:sub(1, 2) - for _, p in ipairs(PATTERNS) do - local path, pattern, code = nil, p[1], p[2] - if signs:find(pattern) then - path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4) - path = WINDOWS and path:gsub("/", "\\") or path - end - if not path then - elseif path:find("[/\\]$") then - -- Mark the ignored directory as `excluded`, so we can process it further within `propagate_down` - return code == CODES.ignored and CODES.excluded or code, path:sub(1, -2) - else - return code, 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 path, code in pairs(changed) do - if code ~= CODES.ignored then - local url = Url(path).parent - while url and url ~= empty do - local s = tostring(url) - new[s] = (new[s] or CODES.unknown) > code and new[s] or code - url = url.parent - end - end - end - return new -end - -local function propagate_down(excluded, cwd, repo) - local new, rel = {}, cwd:strip_prefix(repo) - for _, path in ipairs(excluded) do - if rel:starts_with(path) then - -- If `cwd` is a subdirectory of an excluded directory, also mark it as `excluded` - new[tostring(cwd)] = CODES.excluded - elseif cwd == repo:join(path).parent then - -- If `path` is a direct subdirectory of `cwd`, mark it as `ignored` - new[path] = CODES.ignored - else - -- Skipping, we only care about `cwd` itself and its direct subdirectories for maximum performance - 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 path, code in pairs(changed) do - if code == CODES.unknown then - st.repos[repo][path] = nil - elseif code == CODES.excluded then - -- Mark the directory with a special value `excluded` so that it can be distinguished during UI rendering - st.dirs[path] = CODES.excluded - else - st.repos[repo][path] = code - end - end - ya.render() -end) - -local remove = ya.sync(function(st, cwd) - local repo = st.dirs[cwd] - if not repo then - return - end - - ya.render() - st.dirs[cwd] = nil - if not st.repos[repo] then - return - end - - for _, r in pairs(st.dirs) do - if r == repo then - return - end - end - st.repos[repo] = nil -end) - -local function setup(st, opts) - st.dirs = {} -- Mapping between a directory and its corresponding repository - st.repos = {} -- Mapping between a repository and the status of each of its files - - opts = opts or {} - opts.order = opts.order or 1500 - - local t = th.git or {} - local styles = { - [CODES.ignored] = t.ignored and ui.Style(t.ignored) or ui.Style():fg("darkgray"), - [CODES.untracked] = t.untracked and ui.Style(t.untracked) or ui.Style():fg("magenta"), - [CODES.modified] = t.modified and ui.Style(t.modified) or ui.Style():fg("yellow"), - [CODES.added] = t.added and ui.Style(t.added) or ui.Style():fg("green"), - [CODES.deleted] = t.deleted and ui.Style(t.deleted) or ui.Style():fg("red"), - [CODES.updated] = t.updated and ui.Style(t.updated) or ui.Style():fg("yellow"), - } - local signs = { - [CODES.ignored] = t.ignored_sign or "", - [CODES.untracked] = t.untracked_sign or "?", - [CODES.modified] = t.modified_sign or "", - [CODES.added] = t.added_sign or "", - [CODES.deleted] = t.deleted_sign or "", - [CODES.updated] = t.updated_sign or "", - } - - Linemode:children_add(function(self) - local url = self._file.url - local repo = st.dirs[tostring(url.base)] - local code - if repo then - code = repo == CODES.excluded and CODES.ignored or st.repos[repo][tostring(url):sub(#repo + 2)] - end - - if not code or signs[code] == "" then - return "" - elseif self._file.is_hovered then - return ui.Line { " ", signs[code] } - else - return ui.Line { " ", ui.Span(signs[code]):style(styles[code]) } - end - end, opts.order) -end - -local function fetch(_, job) - local cwd = job.files[1].url.base - local repo = root(cwd) - if not repo then - remove(tostring(cwd)) - return true - end - - local paths = {} - for _, file in ipairs(job.files) do - paths[#paths + 1] = tostring(file.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 - return true, Err("Cannot spawn `git` command, error: %s", err) - end - - local changed, excluded = {}, {} - for line in output.stdout:gmatch("[^\r\n]+") do - local code, path = match(line) - if code == CODES.excluded then - excluded[#excluded + 1] = path - else - changed[path] = code - end - end - - if job.files[1].cha.is_dir then - ya.dict_merge(changed, bubble_up(changed)) - end - ya.dict_merge(changed, propagate_down(excluded, cwd, Url(repo))) - - -- Reset the status of any files that don't appear in the output of `git status` to `unknown`, - -- so that cleaning up outdated statuses from `st.repos` - for _, path in ipairs(paths) do - local s = path:sub(#repo + 2) - changed[s] = changed[s] or CODES.unknown - end - - add(tostring(cwd), repo, changed) - - return false -end - -return { setup = setup, fetch = fetch } From cb49c2cb7a9c528f8f91fa54b2d71ad36ec442e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Dom=C3=ADnguez?= Date: Sat, 10 May 2025 09:21:19 -0400 Subject: [PATCH 3/8] remove since I no longer use it --- .config/yazi/plugins/starship.yazi/LICENSE | 21 ---- .config/yazi/plugins/starship.yazi/README.md | 108 ---------------- .config/yazi/plugins/starship.yazi/main.lua | 123 ------------------- 3 files changed, 252 deletions(-) delete mode 100644 .config/yazi/plugins/starship.yazi/LICENSE delete mode 100644 .config/yazi/plugins/starship.yazi/README.md delete mode 100644 .config/yazi/plugins/starship.yazi/main.lua diff --git a/.config/yazi/plugins/starship.yazi/LICENSE b/.config/yazi/plugins/starship.yazi/LICENSE deleted file mode 100644 index c03ce66..0000000 --- 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 b2b4dd8..0000000 --- a/.config/yazi/plugins/starship.yazi/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# starship.yazi - -Starship prompt plugin for [Yazi](https://github.com/sxyazi/yazi) - - - -## Requirements - -- [Yazi](https://github.com/sxyazi/yazi) (v25.4.8+) -- [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() -``` - -Make sure you have [starship](https://github.com/starship/starship) installed and in your `PATH`. - -## Config - -Here is an example with all available config options: - -```lua -require("starship"):setup({ - -- Hide flags (such as filter, find and search). This is recommended for starship themes which - -- are intended to go across the entire width of the terminal. - hide_flags = false, -- Default: false - -- Whether to place flags after the starship prompt. False means the flags will be placed before the prompt. - flags_after_prompt = true, -- Default: true - -- Custom starship configuration file to use - config_file = "~/.config/starship_full.toml", -- Default: nil -}) -``` - -## 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.Bar.TOP):area(ui.Rect.default) - 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]:pad(ui.Pad.y(1)), - c[2]:pad(ui.Pad(1, c[3].w > 0 and 0 or 1, 1, c[1].w > 0 and 0 or 1)), - c[3]:pad(ui.Pad.y(1)), - } - - local style = th.mgr.border_style - self._base = ya.list_merge(self._base or {}, { - ui.Bar(ui.Bar.RIGHT):area(self._chunks[1]):style(style), - ui.Bar(ui.Bar.LEFT):area(self._chunks[1]):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 -``` - -
- -> [!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/main.lua b/.config/yazi/plugins/starship.yazi/main.lua deleted file mode 100644 index c4d3943..0000000 --- a/.config/yazi/plugins/starship.yazi/main.lua +++ /dev/null @@ -1,123 +0,0 @@ ---- @since 25.4.8 - --- For development ---[[ local function notify(message) ]] ---[[ ya.notify({ title = "Starship", content = message, timeout = 3 }) ]] ---[[ end ]] - -local save = ya.sync(function(st, _cwd, output) - st.output = output - ya.render() -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 - ---@field hide_flags boolean Whether to hide all flags (such as filter and search). Recommended for themes which are intended to take the full width of the terminal. - ---@field flags_after_prompt boolean Whether to place flags (such as filter and search) after the starship prompt. By default this is true. - - --- Setup plugin - --- @param st table State - --- @param args SetupArgs|nil - setup = function(st, args) - local hide_flags = false - local flags_after_prompt = true - - -- Check setup args - if args ~= nil then - if 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 - - if args.hide_flags ~= nil then - hide_flags = args.hide_flags - end - - if args.flags_after_prompt ~= nil then - flags_after_prompt = args.flags_after_prompt - end - end - - -- Replace default header widget - Header:children_remove(1, Header.LEFT) - Header:children_add(function(self) - local max = self._area.w - self._right_width - if max <= 0 then - return "" - end - - if hide_flags or not st.output then - return ui.Line.parse(st.output or "") - end - - -- Split `st.output` at the first line break (or keep as is if none was found) - local output = st.output:match("([^\n]*)\n?") or st.output - - local flags = self:flags() - if flags_after_prompt then - output = output .. " " .. flags - else - output = flags .. " " .. output - end - - return ui.Line.parse(output) - end, 1000, Header.LEFT) - - -- 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, - ya.quote(tostring(cwd), true), - }) - end - end - - -- Subscribe to events - ps.sub("cd", callback) - ps.sub("tab", callback) - end, - - entry = function(_, job) - local args = job.args - local command = Command("starship") - :arg("prompt") - :stdin(Command.INHERIT) - :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, -} From ca4cac86f7ac70654373d7d3ac9da0d7b42b255a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Dom=C3=ADnguez?= Date: Sat, 10 May 2025 19:02:09 -0400 Subject: [PATCH 4/8] actually, it's quite useful so adding it back --- .config/yazi/plugins/git.yazi/LICENSE | 21 +++ .config/yazi/plugins/git.yazi/README.md | 78 ++++++++ .config/yazi/plugins/git.yazi/main.lua | 228 ++++++++++++++++++++++++ 3 files changed, 327 insertions(+) create mode 100644 .config/yazi/plugins/git.yazi/LICENSE create mode 100644 .config/yazi/plugins/git.yazi/README.md create mode 100644 .config/yazi/plugins/git.yazi/main.lua diff --git a/.config/yazi/plugins/git.yazi/LICENSE b/.config/yazi/plugins/git.yazi/LICENSE new file mode 100644 index 0000000..fb5b1d6 --- /dev/null +++ b/.config/yazi/plugins/git.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 yazi-rs + +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/git.yazi/README.md b/.config/yazi/plugins/git.yazi/README.md new file mode 100644 index 0000000..4c5b07c --- /dev/null +++ b/.config/yazi/plugins/git.yazi/README.md @@ -0,0 +1,78 @@ +# git.yazi + +> [!NOTE] +> Yazi v25.2.26 or later is required for this plugin to work. + +Show the status of Git file changes as linemode in the file list. + +https://github.com/user-attachments/assets/34976be9-a871-4ffe-9d5a-c4cdd0bf4576 + +## Installation + +```sh +ya pack -a yazi-rs/plugins:git +``` + +## Setup + +Add the following to your `~/.config/yazi/init.lua`: + +```lua +require("git"):setup() +``` + +And register it as fetchers in your `~/.config/yazi/yazi.toml`: + +```toml +[[plugin.prepend_fetchers]] +id = "git" +name = "*" +run = "git" + +[[plugin.prepend_fetchers]] +id = "git" +name = "*/" +run = "git" +``` + +## Advanced + +You can customize the [Style](https://yazi-rs.github.io/docs/plugins/layout#style) of the status sign with: + +- `th.git.modified` +- `th.git.added` +- `th.git.untracked` +- `th.git.ignored` +- `th.git.deleted` +- `th.git.updated` + +For example: + +```lua +-- ~/.config/yazi/init.lua +th.git = th.git or {} +th.git.modified = ui.Style():fg("blue") +th.git.deleted = ui.Style():fg("red"):bold() +``` + +You can also customize the text of the status sign with: + +- `th.git.modified_sign` +- `th.git.added_sign` +- `th.git.untracked_sign` +- `th.git.ignored_sign` +- `th.git.deleted_sign` +- `th.git.updated_sign` + +For example: + +```lua +-- ~/.config/yazi/init.lua +th.git = th.git or {} +th.git.modified_sign = "M" +th.git.deleted_sign = "D" +``` + +## License + +This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file. diff --git a/.config/yazi/plugins/git.yazi/main.lua b/.config/yazi/plugins/git.yazi/main.lua new file mode 100644 index 0000000..d8f365a --- /dev/null +++ b/.config/yazi/plugins/git.yazi/main.lua @@ -0,0 +1,228 @@ +--- @since 25.4.4 + +local WINDOWS = ya.target_family() == "windows" + +-- The code of supported git status, +-- also used to determine which status to show for directories when they contain different statuses +-- see `bubble_up` +local CODES = { + excluded = 100, -- ignored directory + ignored = 6, -- ignored file + untracked = 5, + modified = 4, + added = 3, + deleted = 2, + updated = 1, + unknown = 0, +} + +local PATTERNS = { + { "!$", CODES.ignored }, + { "?$", CODES.untracked }, + { "[MT]", CODES.modified }, + { "[AC]", CODES.added }, + { "D", CODES.deleted }, + { "U", CODES.updated }, + { "[AD][AD]", CODES.updated }, +} + +local function match(line) + local signs = line:sub(1, 2) + for _, p in ipairs(PATTERNS) do + local path, pattern, code = nil, p[1], p[2] + if signs:find(pattern) then + path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4) + path = WINDOWS and path:gsub("/", "\\") or path + end + if not path then + elseif path:find("[/\\]$") then + -- Mark the ignored directory as `excluded`, so we can process it further within `propagate_down` + return code == CODES.ignored and CODES.excluded or code, path:sub(1, -2) + else + return code, 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 path, code in pairs(changed) do + if code ~= CODES.ignored then + local url = Url(path).parent + while url and url ~= empty do + local s = tostring(url) + new[s] = (new[s] or CODES.unknown) > code and new[s] or code + url = url.parent + end + end + end + return new +end + +local function propagate_down(excluded, cwd, repo) + local new, rel = {}, cwd:strip_prefix(repo) + for _, path in ipairs(excluded) do + if rel:starts_with(path) then + -- If `cwd` is a subdirectory of an excluded directory, also mark it as `excluded` + new[tostring(cwd)] = CODES.excluded + elseif cwd == repo:join(path).parent then + -- If `path` is a direct subdirectory of `cwd`, mark it as `ignored` + new[path] = CODES.ignored + else + -- Skipping, we only care about `cwd` itself and its direct subdirectories for maximum performance + 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 path, code in pairs(changed) do + if code == CODES.unknown then + st.repos[repo][path] = nil + elseif code == CODES.excluded then + -- Mark the directory with a special value `excluded` so that it can be distinguished during UI rendering + st.dirs[path] = CODES.excluded + else + st.repos[repo][path] = code + end + end + ya.render() +end) + +local remove = ya.sync(function(st, cwd) + local repo = st.dirs[cwd] + if not repo then + return + end + + ya.render() + st.dirs[cwd] = nil + if not st.repos[repo] then + return + end + + for _, r in pairs(st.dirs) do + if r == repo then + return + end + end + st.repos[repo] = nil +end) + +local function setup(st, opts) + st.dirs = {} -- Mapping between a directory and its corresponding repository + st.repos = {} -- Mapping between a repository and the status of each of its files + + opts = opts or {} + opts.order = opts.order or 1500 + + local t = th.git or {} + local styles = { + [CODES.ignored] = t.ignored and ui.Style(t.ignored) or ui.Style():fg("darkgray"), + [CODES.untracked] = t.untracked and ui.Style(t.untracked) or ui.Style():fg("magenta"), + [CODES.modified] = t.modified and ui.Style(t.modified) or ui.Style():fg("yellow"), + [CODES.added] = t.added and ui.Style(t.added) or ui.Style():fg("green"), + [CODES.deleted] = t.deleted and ui.Style(t.deleted) or ui.Style():fg("red"), + [CODES.updated] = t.updated and ui.Style(t.updated) or ui.Style():fg("yellow"), + } + local signs = { + [CODES.ignored] = t.ignored_sign or "", + [CODES.untracked] = t.untracked_sign or "?", + [CODES.modified] = t.modified_sign or "", + [CODES.added] = t.added_sign or "", + [CODES.deleted] = t.deleted_sign or "", + [CODES.updated] = t.updated_sign or "", + } + + Linemode:children_add(function(self) + local url = self._file.url + local repo = st.dirs[tostring(url.base)] + local code + if repo then + code = repo == CODES.excluded and CODES.ignored or st.repos[repo][tostring(url):sub(#repo + 2)] + end + + if not code or signs[code] == "" then + return "" + elseif self._file.is_hovered then + return ui.Line { " ", signs[code] } + else + return ui.Line { " ", ui.Span(signs[code]):style(styles[code]) } + end + end, opts.order) +end + +local function fetch(_, job) + local cwd = job.files[1].url.base + local repo = root(cwd) + if not repo then + remove(tostring(cwd)) + return true + end + + local paths = {} + for _, file in ipairs(job.files) do + paths[#paths + 1] = tostring(file.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 + return true, Err("Cannot spawn `git` command, error: %s", err) + end + + local changed, excluded = {}, {} + for line in output.stdout:gmatch("[^\r\n]+") do + local code, path = match(line) + if code == CODES.excluded then + excluded[#excluded + 1] = path + else + changed[path] = code + end + end + + if job.files[1].cha.is_dir then + ya.dict_merge(changed, bubble_up(changed)) + end + ya.dict_merge(changed, propagate_down(excluded, cwd, Url(repo))) + + -- Reset the status of any files that don't appear in the output of `git status` to `unknown`, + -- so that cleaning up outdated statuses from `st.repos` + for _, path in ipairs(paths) do + local s = path:sub(#repo + 2) + changed[s] = changed[s] or CODES.unknown + end + + add(tostring(cwd), repo, changed) + + return false +end + +return { setup = setup, fetch = fetch } From e3183be5f14bbef2651896485b25021fa387fb29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Dom=C3=ADnguez?= Date: Sat, 10 May 2025 19:04:40 -0400 Subject: [PATCH 5/8] add yatline-dracula plugin --- .../plugins/yatline-dracula.yazi/main.lua | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .config/yazi/plugins/yatline-dracula.yazi/main.lua diff --git a/.config/yazi/plugins/yatline-dracula.yazi/main.lua b/.config/yazi/plugins/yatline-dracula.yazi/main.lua new file mode 100644 index 0000000..3bc9c55 --- /dev/null +++ b/.config/yazi/plugins/yatline-dracula.yazi/main.lua @@ -0,0 +1,62 @@ +--==================-- +-- Dracula Theme -- +--==================-- + +local dracula_palette = { + bg = "#282a36", -- Dracula background + bg_highlight = "#44475a", -- Dracula current line/selection + fg = "#f8f8f2", -- Dracula foreground + blue = "#6272a4", -- Dracula comment + cyan = "#8be9fd", -- Dracula cyan + green = "#50fa7b", -- Dracula green + orange = "#ffb86c", -- Dracula orange + pink = "#ff79c6", -- Dracula pink + purple = "#bd93f9", -- Dracula purple + red = "#ff5555", -- Dracula red + yellow = "#f1fa8c", -- Dracula yellow +} + +--- Gets the Dracula theme. +--- @return table theme Used in Yatline. +local function dracula_theme() + local palette = dracula_palette + + return { + section_separator = { open = "", close = "" }, + part_separator = { open = "", close = "" }, + inverse_separator = { open = "", close = "" }, + ---#=== yatline ===#--- + style_a = { + fg = palette.bg, + bg_mode = { + normal = palette.purple, -- Using purple as primary mode color + select = palette.pink, -- Using pink for select mode + un_set = palette.red, -- Keeping red for unset mode + }, + }, + style_b = { bg = palette.blue, fg = palette.fg }, + style_c = { bg = palette.bg_highlight, fg = palette.fg }, + + permissions_t_fg = palette.cyan, -- Using cyan for 't' permissions + permissions_r_fg = palette.yellow, -- Using yellow for 'r' permissions + permissions_w_fg = palette.red, -- Using red for 'w' permissions + permissions_x_fg = palette.green, -- Using green for 'x' permissions + permissions_s_fg = palette.fg, -- Using default fg for 's' permissions + + selected = { icon = "󰻭", fg = palette.pink }, -- Using pink for selected items + copied = { icon = "", fg = palette.green }, -- Using green for copied items + cut = { icon = "", fg = palette.red }, -- Using red for cut items + + total = { icon = "󰮍", fg = palette.yellow }, -- Using yellow for totals + succ = { icon = "", fg = palette.green }, -- Using green for success + fail = { icon = "", fg = palette.red }, -- Using red for failures + found = { icon = "󰮕", fg = palette.cyan }, -- Using cyan for found items + processed = { icon = "󰐍", fg = palette.orange }, -- Using orange for processed items + } +end + +return { + setup = function() + return dracula_theme() + end, +} From 9ae6daae329584267a6777b3fa4e2b4f8f5126cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Dom=C3=ADnguez?= Date: Sat, 10 May 2025 19:05:27 -0400 Subject: [PATCH 6/8] add yatline-githead plugin --- .../plugins/yatline-githead.yazi/main.lua | 314 ++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 .config/yazi/plugins/yatline-githead.yazi/main.lua diff --git a/.config/yazi/plugins/yatline-githead.yazi/main.lua b/.config/yazi/plugins/yatline-githead.yazi/main.lua new file mode 100644 index 0000000..73e2df0 --- /dev/null +++ b/.config/yazi/plugins/yatline-githead.yazi/main.lua @@ -0,0 +1,314 @@ +---@diagnostic disable: undefined-global + +local save = ya.sync(function(this, cwd, output) + if cx.active.current.cwd == Url(cwd) then + this.output = output + ya.render() + end +end) + +return { + setup = function(this, options) + options = options or {} + + local config = { + show_branch = options.show_branch == nil and true or options.show_branch, + branch_prefix = options.branch_prefix or "on", + branch_symbol = options.branch_symbol or "", + branch_borders = options.branch_borders or "()", + + commit_symbol = options.commit_symbol or "@", + + show_behind_ahead = options.behind_ahead == nil and true or options.behind_ahead, + behind_symbol = options.behind_symbol or "⇣", + ahead_symbol = options.ahead_symbol or "⇡", + + show_stashes = options.show_stashes == nil and true or options.show_stashes, + stashes_symbol = options.stashes_symbol or "$", + + show_state = options.show_state == nil and true or options.show_state, + show_state_prefix = options.show_state_prefix == nil and true or options.show_state_prefix, + state_symbol = options.state_symbol or "~", + + show_staged = options.show_staged == nil and true or options.show_staged, + staged_symbol = options.staged_symbol or "+", + + show_unstaged = options.show_unstaged == nil and true or options.show_unstaged, + unstaged_symbol = options.unstaged_symbol or "!", + + show_untracked = options.show_untracked == nil and true or options.show_untracked, + untracked_symbol = options.untracked_symbol or "?", + } + + if options.theme then + options = options.theme + end + + local theme = { + prefix_color = options.prefix_color or "white", + branch_color = options.branch_color or "blue", + commit_color = options.commit_color or "bright magenta", + behind_color = options.behind_color or "bright magenta", + ahead_color = options.ahead_color or "bright magenta", + stashes_color = options.stashes_color or "bright magenta", + state_color = options.state_color or "red", + staged_color = options.staged_color or "bright yellow", + unstaged_color = options.unstaged_color or "bright yellow", + untracked_color = options.untracked_color or "bright blue", + } + + local function get_branch(status) + local branch = status:match("On branch (%S+)") + + if branch == nil then + local commit = status:match("onto (%S+)") or status:match("detached at (%S+)") + + if commit == nil then + return "" + else + local branch_prefix = config.branch_prefix == "" and " " or " " .. config.branch_prefix .. " " + local commit_prefix = config.commit_symbol == "" and "" or config.commit_symbol + + return { "commit", branch_prefix .. commit_prefix, commit } + end + else + local left_border = config.branch_borders:sub(1, 1) + local right_border = config.branch_borders:sub(2, 2) + + local branch_string = "" + + if config.branch_symbol == "" then + branch_string = left_border .. branch .. right_border + else + branch_string = left_border .. config.branch_symbol .. " " .. branch .. right_border + end + + local branch_prefix = config.branch_prefix == "" and " " or " " .. config.branch_prefix .. " " + + return { "branch", branch_prefix, branch_string } + end + end + + local function get_behind_ahead(status) + local diverged_ahead, diverged_behind = status:match("have (%d+) and (%d+) different") + if diverged_ahead and diverged_behind then + return { " " .. config.behind_symbol .. diverged_behind, config.ahead_symbol .. diverged_ahead } + else + local behind = status:match("behind %S+ by (%d+) commit") + local ahead = status:match("ahead of %S+ by (%d+) commit") + if ahead then + return { "", " " .. config.ahead_symbol .. ahead } + elseif behind then + return { " " .. config.behind_symbol .. behind, "" } + else + return "" + end + end + end + + local function get_stashes(status) + local stashes = tonumber(status:match("Your stash currently has (%S+)")) + + return stashes ~= nil and " " .. config.stashes_symbol .. stashes or "" + end + + local function get_state(status) + local result = status:match("Unmerged paths:%s*(.-)%s*\n\n") + if result then + local filtered_result = result:gsub("^[%s]*%b()[%s]*", ""):gsub("^[%s]*%b()[%s]*", "") + + local unmerged = 0 + for line in filtered_result:gmatch("[^\r\n]+") do + if line:match("%S") then + unmerged = unmerged + 1 + end + end + + local state_name = "" + + if config.show_state_prefix then + if status:find("git merge") then + state_name = "merge " + elseif status:find("git cherry%-pick") then + state_name = "cherry " + elseif status:find("git rebase") then + state_name = "rebase " + + if status:find("done") then + local done = status:match("%((%d+) com.- done%)") or "" + state_name = state_name .. done .. "/" .. unmerged .. " " + end + else + state_name = "" + end + end + + return " " .. state_name .. config.state_symbol .. unmerged + else + return "" + end + end + + local function get_staged(status) + local result = status:match("Changes to be committed:%s*(.-)%s*\n\n") + if result then + local filtered_result = result:gsub("^[%s]*%b()[%s]*", "") + + local staged = 0 + for line in filtered_result:gmatch("[^\r\n]+") do + if line:match("%S") then + staged = staged + 1 + end + end + + return " " .. config.staged_symbol .. staged + else + return "" + end + end + + local function get_unstaged(status) + local result = status:match("Changes not staged for commit:%s*(.-)%s*\n\n") + if result then + local filtered_result = result:gsub("^[%s]*%b()[\r\n]*", ""):gsub("^[%s]*%b()[\r\n]*", "") + + local unstaged = 0 + for line in filtered_result:gmatch("[^\r\n]+") do + if line:match("%S") then + unstaged = unstaged + 1 + end + end + + return " " .. config.unstaged_symbol .. unstaged + else + return "" + end + end + + local function get_untracked(status) + local result = status:match("Untracked files:%s*(.-)%s*\n\n") + if result then + local filtered_result = result:gsub("^[%s]*%b()[\r\n]*", "") + + local untracked = 0 + for line in filtered_result:gmatch("[^\r\n]+") do + if line:match("%S") then + untracked = untracked + 1 + end + end + + return " " .. config.untracked_symbol .. untracked + else + return "" + end + end + + function Header:githead() + local status = this.output + + local branch_array = get_branch(status) + local prefix = ui.Span(config.show_branch and branch_array[2] or ""):fg(theme.prefix_color) + local branch = ui.Span(config.show_branch and branch_array[3] or "") + :fg(branch_array[1] == "commit" and theme.commit_color or theme.branch_color) + local behind_ahead = get_behind_ahead(status) + local behind = ui.Span(config.show_behind_ahead and behind_ahead[1] or ""):fg(theme.behind_color) + local ahead = ui.Span(config.show_behind_ahead and behind_ahead[2] or ""):fg(theme.ahead_color) + local stashes = ui.Span(config.show_stashes and get_stashes(status) or ""):fg(theme.stashes_color) + local state = ui.Span(config.show_state and get_state(status) or ""):fg(theme.state_color) + local staged = ui.Span(config.show_staged and get_staged(status) or ""):fg(theme.staged_color) + local unstaged = ui.Span(config.show_unstaged and get_unstaged(status) or ""):fg(theme.unstaged_color) + local untracked = ui.Span(config.show_untracked and get_untracked(status) or ""):fg(theme.untracked_color) + + return ui.Line({ prefix, branch, behind, ahead, stashes, state, staged, unstaged, untracked }) + end + + Header:children_add(Header.githead, 2000, Header.LEFT) + + local callback = function() + local cwd = cx.active.current.cwd + + if this.cwd ~= cwd then + this.cwd = cwd + ya.manager_emit("plugin", { + this._id, + ya.quote(tostring(cwd), true), + }) + end + end + + ps.sub("cd", callback) + ps.sub("tab", callback) + + if Yatline ~= nil then + function Yatline.coloreds.get:githead() + local status = this.output + local githead = {} + + local branch = config.show_branch and get_branch(status) or "" + if branch ~= nil and branch ~= "" then + table.insert(githead, { branch[2], theme.prefix_color }) + if branch[1] == "commit" then + table.insert(githead, { branch[3], theme.commit_color }) + else + table.insert(githead, { branch[3], theme.branch_color }) + end + end + + local behind_ahead = config.show_behind_ahead and get_behind_ahead(status) or "" + if behind_ahead ~= nil and behind_ahead ~= "" then + if behind_ahead[1] ~= nil and behind_ahead[1] ~= "" then + table.insert(githead, { behind_ahead[1], theme.behind_color }) + elseif behind_ahead[2] ~= nil and behind_ahead[2] ~= "" then + table.insert(githead, { behind_ahead[2], theme.ahead_color }) + end + end + + local stashes = config.show_stashes and get_stashes(status) or "" + if stashes ~= nil and stashes ~= "" then + table.insert(githead, { stashes, theme.stashes_color }) + end + + local state = config.show_state and get_state(status) or "" + if state ~= nil and state ~= "" then + table.insert(githead, { state, theme.state_color }) + end + + local staged = config.show_staged and get_staged(status) or "" + if staged ~= nil and staged ~= "" then + table.insert(githead, { staged, theme.staged_color }) + end + + local unstaged = config.show_unstaged and get_unstaged(status) or "" + if unstaged ~= nil and unstaged ~= "" then + table.insert(githead, { unstaged, theme.unstaged_color }) + end + + local untracked = config.show_untracked and get_untracked(status) or "" + if untracked ~= nil and untracked ~= "" then + table.insert(githead, { untracked, theme.untracked_color }) + end + + if #githead == 0 then + return "" + else + table.insert(githead, { " ", theme.prefix_color }) + return githead + end + end + end + end, + + entry = function(_, job) + local args = job.args or job + local command = Command("git") + :args({ "status", "--ignore-submodules=dirty", "--branch", "--show-stash", "--ahead-behind" }) + :cwd(args[1]) + :env("LANGUAGE", "en_US.UTF-8") + :stdout(Command.PIPED) + local output = command:output() + + if output then + save(args[1], output.stdout) + end + end, +} From cd3c21cac7eb06335b4cdcebb468f9208aa68086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Dom=C3=ADnguez?= Date: Sat, 10 May 2025 19:05:57 -0400 Subject: [PATCH 7/8] add yatline plugin --- .config/yazi/plugins/yatline.yazi/main.lua | 1362 ++++++++++++++++++++ 1 file changed, 1362 insertions(+) create mode 100644 .config/yazi/plugins/yatline.yazi/main.lua diff --git a/.config/yazi/plugins/yatline.yazi/main.lua b/.config/yazi/plugins/yatline.yazi/main.lua new file mode 100644 index 0000000..48591c5 --- /dev/null +++ b/.config/yazi/plugins/yatline.yazi/main.lua @@ -0,0 +1,1362 @@ +--- @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 files_icon +local filtereds_icon + +local selected_fg +local copied_fg +local cut_fg +local files_fg +local filtereds_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 + +--- the number of characters in a UTF-8 string +--- @param s string The string to process. +--- @return integer The number of characters in the string. +local function utf8len(s) + -- count the number of non-continuing bytes + return select(2, s:gsub("[^\128-\193]", "")) +end + +--- like string.sub() but i, j are utf8 strings +--- a utf8-safe string.sub() +--- @param s string The string to process. +--- @param i integer The start position. +--- @param j integer The end position. +--- @return string The substring. +local function utf8sub(s, i, j) + -- pattern for matching UTF-8 characters + local pattern = "[%z\1-\127\194-\244][\128-\191]*" + + -- helper function for position calculation + --- @param pos integer The position of the character. + --- @param len integer The length of the string. + --- @return integer The relative position of the character. + local function posrelat(pos, len) + if pos < 0 then + pos = len + pos + 1 + end + return pos + end + + -- helper function to iterate over UTF-8 chars + local function chars(_s, no_subs) + local function map(f) + local _i = 0 + if no_subs then + for b, e in _s:gmatch("()" .. pattern .. "()") do + _i = _i + 1 + local c = e - b + f(_i, c, b) + end + else + for b, c in _s:gmatch("()(" .. pattern .. ")") do + _i = _i + 1 + f(_i, c, b) + end + end + end + return coroutine.wrap(function() + return map(coroutine.yield) + end) + end + + local l = utf8len(s) + + i = posrelat(i, l) + j = j and posrelat(j, l) or l + + if i < 1 then + i = 1 + end + if j > l then + j = l + end + + if i > j then + return "" + end + + local diff = j - i + local iter = chars(s, true) + + -- advance up to i + for _ = 1, i - 1 do + iter() + end + + local c, b = select(2, iter()) + + -- becareful with the edge case of empty string + if not b then + return "" + end + + -- i and j are the same, single-character sub + if diff == 0 then + return string.sub(s, b, b + c - 1) + end + + i = b + + -- advance up to j + for _ = 1, diff - 1 do + iter() + end + + c, b = select(2, iter()) + + return string.sub(s, i, b + c - 1) +end + +--- Trims the filename if it is longer than the max_length. +--- @param filename string The name of a file which will be trimmed. +--- @param max_length integer Maximum length of the filename. +--- @param trim_length integer Length of the trimmed filename. +--- @return string trimmed_filename Trimmed filename. +local function trim_filename(filename, max_length, trim_length) + if not max_length or not trim_length then + return filename + end + + -- Count UTF-8 characters + local len = utf8len(filename) + + if len <= max_length then + return filename + end + + if len <= trim_length * 2 then + return filename + end + + return utf8sub(filename, 1, trim_length) .. "..." .. utf8sub(filename, len - trim_length + 1, len) +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 + +--- Configuration for getting hovered file's name +--- @class HoveredNameConfig +--- @field trimed? boolean Whether to trim the filename if it's too long (default: false) +--- @field max_length? integer Maximum length of the filename (default: 24) +--- @field trim_length? integer Length of each end when trimming (default: 10) +--- @field show_symlink? boolean Whether to show symlink target (default: false) +--- Gets the hovered file's name of the current active tab. +--- @param config? HoveredNameConfig Configuration for getting hovered file's name +--- @return string name Current active tab's hovered file's name +function Yatline.string.get:hovered_name(config) + local hovered = cx.active.current.hovered + if not hovered then + return "" + end + + if not config then + return hovered.name + end + + local trimed = config.trimed or false + local max_length = config.max_length or 24 + local trim_length = config.trim_length or 10 + local show_symlink = config.show_symlink or false + + local link_delimiter = " -> " + local linked = (show_symlink and hovered.link_to ~= nil) and (link_delimiter .. tostring(hovered.link_to)) or "" + + if trimed then + local trimmed_name = trim_filename(hovered.name, max_length, trim_length) + local trimmed_linked = #linked ~= 0 + and link_delimiter .. trim_filename( + string.sub(linked, #link_delimiter + 1, -1), + max_length, + trim_length + ) + or "" + return trimmed_name .. trimmed_linked + else + return hovered.name .. linked + end +end + +--- Configuration for getting hovered file's path +--- @class HoveredPathConfig +--- @field trimed? boolean Whether to trim the file path if it's too long (default: false) +--- @field max_length? integer Maximum length of the file path (default: 24) +--- @field trim_length? integer Length of each end when trimming (default: 10) +--- Gets the hovered file's path of the current active tab. +--- @param config? HoveredPathConfig Configuration for getting hovered file's path +--- @return string path Current active tab's hovered file's path. +function Yatline.string.get:hovered_path(config) + local hovered = cx.active.current.hovered + if not hovered then + return "" + end + + if not config then + return ya.readable_path(tostring(hovered.url)) + end + + local trimed = config.trimed or false + local max_length = config.max_length or 24 + local trim_length = config.trim_length or 10 + + if trimed then + return trim_filename(ya.readable_path(tostring(hovered.url)), max_length, trim_length) + else + return ya.readable_path(tostring(hovered.url)) + 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 + +--- Configuration for getting curent active tab's path +--- @class TabPathConfig +--- @field trimed? boolean Whether to trim the current active tab's path if it's too long (default: false) +--- @field max_length? integer Maximum length of the current active tab's path (default: 24) +--- @field trim_length? integer Length of each end when trimming (default: 10) +--- Gets the path of the current active tab. +--- @param config? TabPathConfig Configuration for getting current active tab's path +--- @return string path Current active tab's path. +function Yatline.string.get:tab_path(config) + 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 + + if not config then + return ya.readable_path(tostring(cwd)) .. suffix + end + + local trimed = config.trimed or false + local max_length = config.max_length or 24 + local trim_length = config.trim_length or 10 + + if trimed then + return trim_filename(ya.readable_path(tostring(cwd)), max_length, trim_length) .. suffix + else + return ya.readable_path(tostring(cwd)) .. suffix + end +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 and also number of files or filtered files of the active tab. +--- @param filter? boolean Whether or not number of files (or filtered files) will be shown. +--- @return Coloreds coloreds Active tab's number of selected and yanked files and also number of files or filtered files +function Yatline.coloreds.get:count(filter) + local num_yanked = #cx.yanked + local num_selected = #cx.active.selected + local num_files = #cx.active.current.files + + 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 files_count_fg, files_count_icon + if cx.active.current.files.filter or cx.active.current.cwd.is_search then + files_count_icon = filtereds_icon + files_count_fg = filtereds_fg + else + files_count_icon = files_icon + files_count_fg = files_fg + end + + local coloreds + if filter then + coloreds = { + { string.format(" %s %d ", files_count_icon, num_files), files_count_fg }, + { string.format(" %s %d ", selected_icon, num_selected), selected_fg }, + { string.format(" %s %d ", yanked_icon, num_yanked), yanked_fg }, + } + else + coloreds = { + { string.format(" %s %d ", selected_icon, num_selected), selected_fg }, + { string.format(" %s %d ", yanked_icon, num_yanked), yanked_fg }, + } + end + + 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 = {} }, + } + + config.theme = (not rt.term.light and config.theme_dark) + or (rt.term.light and config.theme_light) + or config.theme + + if config.theme then + for key, value in pairs(config.theme) do + if not config[key] then + config[key] = value + end + end + 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.files then + files_icon = config.files.icon + files_fg = config.files.fg + else + files_icon = "" + files_fg = "blue" + end + + if config.filtereds then + filtereds_icon = config.filtereds.icon + filtereds_fg = config.filtereds.fg + else + filtereds_icon = "" + filtereds_fg = "magenta" + 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 + + config = nil + + 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(th.status.progress_normal) + else + gauge = gauge:gauge_style(th.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(th.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, +} From 0f2ed387b330ca2995e3eaeeba4e1aa8d6d73d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Dom=C3=ADnguez?= Date: Sat, 10 May 2025 19:06:41 -0400 Subject: [PATCH 8/8] various config updates --- .config/yazi/init.lua | 83 ++++++++++++++++++++++++++++++++++++------ .config/yazi/yazi.toml | 2 +- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/.config/yazi/init.lua b/.config/yazi/init.lua index 99e606a..81efd17 100644 --- a/.config/yazi/init.lua +++ b/.config/yazi/init.lua @@ -45,15 +45,76 @@ Header:children_add(function() end return ui.Span(ya.user_name() .. "@" .. ya.host_name() .. ":"):fg("blue") end, 500, Header.LEFT) --- startship prompt -require("starship"):setup({ - -- Hide flags (such as filter, find and search). This is recommended for starship themes which - -- are intended to go across the entire width of the terminal. - hide_flags = false, -- Default: false - -- Whether to place flags after the starship prompt. False means the flags will be placed before the prompt. - flags_after_prompt = true, -- Default: true - -- Custom starship configuration file to use - config_file = "~/.config/starship_full.toml", -- Default: nil -}) --- git for yazi +--git require("git"):setup() +-- yatline dracula_theme +local dracula_theme = require("yatline-dracula"):setup() +-- yatline +require("yatline"):setup({ + theme = dracula_theme, + section_separator = { open = "", close = "" }, + part_separator = { open = "", close = "" }, + inverse_separator = { open = "", close = "" }, + show_background = false, + tab_width = 0, + + header_line = { + left = { + section_a = { + { type = "coloreds", custom = false, name = "tab_path" }, + {type = "line", custom = false, name = "tabs", params = {"left"}}, + }, + section_b = { + {type = "coloreds", custom = false, name = "githead"}, + }, + section_c = { + } + }, + right = { + section_a = { + + + }, + section_b = { + + }, + section_c = { + {type = "coloreds", custom = false, name = "count"}, + } + } + }, + + status_line = { + left = { + section_a = { + {type = "string", custom = false, name = "tab_mode"}, + }, + section_b = { + {type = "string", custom = false, name = "hovered_size"}, + }, + section_c = { + + {type = "string", custom = false, name = "hovered_path"}, + + } + }, + right = { + section_a = { + {type = "string", custom = false, name = "cursor_position"}, + }, + section_b = { + {type = "string", custom = false, name = "cursor_percentage"}, + }, + section_c = { + + + {type = "coloreds", custom = false, name = "permissions"}, + } + } + }, +}) +-- yatline-tab-path +require("yatline-tab-path"):setup() +-- yatline-githead +require("yatline-githead"):setup() + diff --git a/.config/yazi/yazi.toml b/.config/yazi/yazi.toml index 49c88e1..6cbf958 100644 --- a/.config/yazi/yazi.toml +++ b/.config/yazi/yazi.toml @@ -1,6 +1,6 @@ [manager] show_hidden = true -linemode = "size_and_mtime" +linemode = "owner" [plugin] prepend_previewers = [