diff options
Diffstat (limited to 'config/yazi/plugins/yatline-githead.yazi')
| -rw-r--r-- | config/yazi/plugins/yatline-githead.yazi/LICENSE | 22 | ||||
| -rw-r--r-- | config/yazi/plugins/yatline-githead.yazi/README.md | 213 | ||||
| -rw-r--r-- | config/yazi/plugins/yatline-githead.yazi/main.lua | 569 |
3 files changed, 804 insertions, 0 deletions
diff --git a/config/yazi/plugins/yatline-githead.yazi/LICENSE b/config/yazi/plugins/yatline-githead.yazi/LICENSE new file mode 100644 index 00000000..0c37e850 --- /dev/null +++ b/config/yazi/plugins/yatline-githead.yazi/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2024 imsi32 +Copyright (c) 2024 llanosrocas + +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/yatline-githead.yazi/README.md b/config/yazi/plugins/yatline-githead.yazi/README.md new file mode 100644 index 00000000..e2af22e2 --- /dev/null +++ b/config/yazi/plugins/yatline-githead.yazi/README.md @@ -0,0 +1,213 @@ +# yatline-githead.yazi + +Manual clone of [githead.yazi](https://github.com/llanosrocas/githead.yazi) for supporting [yatline.yazi](https://github.com/imsi32/yatline.yazi) + +> [!IMPORTANT] +> This repository will not add new features other than coming from original repository. +> The latest commit in there is 317d09f728928943f0af72ff6ce31ea335351202. +> Which can be found in [here](https://github.com/llanosrocas/githead.yazi/commit/317d09f728928943f0af72ff6ce31ea335351202) + +All supported features are listed [here](#features) + +## Requirements + +- yazi version >= 25.5.28. +- Font with symbol support. For example [Nerd Fonts](https://www.nerdfonts.com/). +- [yatline.yazi](https://github.com/imsi32/yatline.yazi) (Mandatory) + +> [!IMPORTANT] +> Recommended to use [githead.yazi](https://github.com/llanosrocas/githead.yazi), if you are not using yatline.yazi + +## Installation + +```sh +ya pkg add imsi32/yatline-githead +``` + +## Usage + +Add this to your `~/.config/yazi/init.lua`, after yatline.yazi configuration: + +```lua +require("yatline-githead"):setup() +``` + +### Default Configuration + +```lua +require("yatline-githead"):setup({ + order = { + "branch", + "remote", + "tag", + "commit", + "behind_ahead_remote", + "stashes", + "state", + "staged", + "unstaged", + "untracked", + }, + + show_numbers = true, -- shows staged, unstaged, untracked, stashes count + + show_branch = true, + branch_prefix = "", + branch_color = "blue", + branch_symbol = "", + branch_borders = "", + + show_remote_branch = true, -- only shown if different from local branch + always_show_remote_branch = false, -- always show remote branch even if it the same as local branch + always_show_remote_repo = false, -- Adds `origin/` if `always_show_remote_branch` is enabled + remote_branch_prefix = ":", + remote_branch_color = "bright magenta", + + show_tag = true, -- only shown if branch is not available + always_show_tag = false, + tag_color = "magenta", + tag_symbol = "#", + + show_commit = true, -- only shown if branch AND tag are not available + always_show_commit = false, + commit_color = "bright magenta", + commit_symbol = "@", + + show_behind_ahead_remote = true, + behind_remote_color = "bright magenta", + behind_remote_symbol = "⇣", + ahead_remote_color = "bright magenta", + ahead_remote_symbol = "⇡", + + show_stashes = true, + stashes_color = "bright magenta", + stashes_symbol = "$", + + show_state = true, + show_state_prefix = true, + state_color = "red", + state_symbol = "~", + + show_staged = true, + staged_color = "bright yellow", + staged_symbol = "+", + + show_unstaged = true, + unstaged_color = "bright yellow", + unstaged_symbol = "!", + + show_untracked = true, + untracked_color = "blue", + untracked_symbol = "?", +}) +``` + +### Theme + +You can also use a [theme](https://github.com/imsi32/yatline-themes): + +```lua +local your_theme = { + branch_color = "blue", + remote_branch_color = "bright magenta", + tag_color = "magenta", + commit_color = "bright magenta", + behind_remote_color = "bright magenta", + ahead_remote_color = "bright magenta", + stashes_color = "bright magenta", + state_color = "red", + staged_color = "bright yellow", + unstaged_color = "bright yellow", + untracked_color = "blue", +} + +require("yatline-githead"):setup({ +-- === + + theme = your_theme, + +-- === +}) +``` + +### Adding as a Component + +If you are using yatline.yazi, you can use this component: + +``` lua +-- === + + {type = "coloreds", custom = false, name = "githead"}, + +-- === +``` + +``` text +/cwd on ( feature):origin/main #v1.0.0 #1234567 ⇣2⇡3 $1 rebase 1/2 ~2 +4 !1 ?5 +| | | | ││ | | | | | | | | | | | | +| | | | ││ | | | | | | | | | | | └─── untracked_symbol +| | | | ││ | | | | | | | | | | └────── unstaged_symbol +| | | | ││ | | | | | | | | | └───────── staged_symbol +| | | | ││ | | | | | | | | └──────────── state_symbol +| | | | ││ | | | | | | | └─────────────────────── state_prefix +| | | | ││ | | | | | | └────────────────────────── stashes_symbol +| | | | ││ | | | | | └───────────────────────────── ahead_symbol +| | | | ││ | | | | └─────────────────────────────── behind_symbol +| | | | ││ | | | └──────────────────────────────────────── commit_symbol +| | | | ││ | | └──────────────────────────────────────────────── tag_symbol +| | | | ││ | └───────────────────────────────────────────────────── remote_branch +| | | | ││ └─────────────────────────────────────────────────────────── remote_repo +| | | | |└───────────────────────────────────────────────────────────── remote_branch_prefix +| | | | └────────────────────────────────────────────────────────────── branch_borders +| | | └──────────────────────────────────────────────────────────────────── branch +| | └─────────────────────────────────────────────────────────────────────── branch_symbol +| └─────────────────────────────────────────────────────────────────────────── branch_prefix +└──────────────────────────────────────────────────────────────────────────────── cwd +``` + +## Features + +- [x] Current branch +- [x] Remote and remote branch +- [x] Latest tag +- [x] Latest commit +- [x] Behind/Ahead of the remote +- [x] Stashes +- [x] States + - [x] merge + - [x] cherry + - [x] rebase (+ done counter) + - [x] revert + - [x] bisect +- [x] Staged +- [x] Unstaged +- [x] Untracked + +### Under the hood + +The goal is to use minimum amount of shell commands. + +- Branch, stashes, staged files, unstaged files, untracked files. + +```shell +git status --ignore-submodules=dirty --branch --show-stash --ahead-behind +``` + +- Remote branch and repo: + +```shell +git rev-parse --abbrev-ref --symbolic-full-name @{upstream} +``` + +- Latest tag and commit: + +```shell +git log --format="commit %h%d" -n 1 +``` + +## Credits + +- [githead.yazi](https://github.com/llanosrocas/githead.yazi) +- [yazi source code](https://github.com/sxyazi/yazi) +- [powerlevel10k](https://github.com/romkatv/powerlevel10k) +- [twio142](https://github.com/twio142/githead.yazi) 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 00000000..ab49987f --- /dev/null +++ b/config/yazi/plugins/yatline-githead.yazi/main.lua @@ -0,0 +1,569 @@ +---@diagnostic disable: undefined-global + +local save = ya.sync(function(this, cwd, output) + if cwd:match("%.git[/\\]") or cwd:match("%.git$") then + this.output = nil + return + end + if cx.active.current.cwd == Url(cwd) then + ---@type GitStatusData + this.output = output + ui.render() + end +end) + +return { + setup = function(this, options) + options = options or {} + + local config = { + order = options.order or { + "branch", + "remote_branch", + "tag", + "commit", + "behind_ahead_remote", + "stashes", + "state", + "staged", + "unstaged", + "untracked", + }, + + show_numbers = options.show_numbers == nil and true or options.show_numbers, + + show_branch = options.show_branch == nil and true or options.show_branch, + branch_prefix = options.branch_prefix or "", + branch_symbol = options.branch_symbol or "", + branch_borders = options.branch_borders or "", + + show_remote_branch = options.show_remote_branch == nil and true or options.show_remote_branch, + always_show_remote_branch = options.always_show_remote_branch == nil and false + or options.always_show_remote_branch, + always_show_remote_repo = options.always_show_remote_repo == nil and false + or options.always_show_remote_repo, + remote_branch_prefix = options.remote_branch_prefix or ":", + + show_tag = options.show_tag == nil and true or options.show_tag, + always_show_tag = options.always_show_tag == nil and false or options.always_show_tag, + tag_symbol = options.tag_symbol == nil and "#" or options.tag_symbol, + + show_commit = options.show_commit == nil and true or options.show_commit, + always_show_commit = options.always_show_commit == nil and false or options.always_show_commit, + commit_symbol = options.commit_symbol == nil and "@" or options.commit_symbol, + + show_behind_ahead_remote = options.show_behind_ahead_remote == nil and true + or options.show_behind_ahead_remote, + behind_remote_symbol = options.behind_remote_symbol or "⇣", + ahead_remote_symbol = options.ahead_remote_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 = { + branch_color = options.branch_color or "blue", + remote_branch_color = options.remote_branch_color or "bright magenta", + tag_color = options.tag_color or "magenta", + commit_color = options.commit_color or "bright magenta", + behind_remote_color = options.behind_remote_color or "bright magenta", + ahead_remote_color = options.ahead_remote_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 render_branch() + local branch = this.output.branch + + if not branch then + return nil + end + + 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_prefix, nil }, + { branch_string, theme.branch_color }, + } + end + + local function render_remote_branch() + local branch = this.output.branch + local remote_branch = this.output.remote_branch + local remote_repo = this.output.remote_repo + + if not remote_branch then + return nil + end + + local show_remote = config.always_show_remote_branch or branch ~= remote_branch + if not show_remote then + return nil + end + + local remote_branch_label = config.always_show_remote_repo and (remote_repo .. "/" .. remote_branch) + or remote_branch + + return { + { config.remote_branch_prefix, nil }, + { remote_branch_label, theme.remote_branch_color }, + } + end + + local function render_tag() + local branch = this.output.branch + local tag = this.output.tag + + if not tag then + return nil + end + + if not branch or config.always_show_tag then + return { + { config.tag_symbol, nil }, + { tag, theme.tag_color }, + } + end + end + + local function render_commit() + local branch = this.output.branch + local tag = this.output.tag + local commit = this.output.commit + + if not commit then + return nil + end + + if (not branch and not tag) or config.always_show_commit then + return { + { config.commit_symbol, nil }, + { commit, theme.commit_color }, + } + end + end + + local function render_behind_ahead_remote() + local behind = this.output.behind_remote + local ahead = this.output.ahead_remote + + local behind_label = behind + and behind > 0 + and { + config.behind_remote_symbol .. (config.show_numbers and behind or ""), + theme.behind_remote_color, + } + + local ahead_label = ahead + and ahead > 0 + and { + config.ahead_remote_symbol .. (config.show_numbers and ahead or ""), + theme.ahead_remote_color, + } + + if ahead_label and behind_label then + return { + behind_label, + ahead_label, + } + elseif ahead_label then + return ahead_label + elseif behind_label then + return behind_label + else + return nil + end + end + + local function render_stashes() + local stashes_count = this.output.stashes + + if not stashes_count then + return nil + end + + local stashes_label = config.stashes_symbol + + if config.show_numbers then + stashes_label = stashes_label .. stashes_count + end + + return { stashes_label, theme.stashes_color } + end + + local function render_state() + local state_prefix = this.output.state_prefix + local unmerged_count = this.output.unmerged_count + local rebase_done = this.output.rebase_done + local rebase_total = this.output.rebase_total + + if state_prefix then + local state_label = "" + if config.show_state_prefix then + state_label = state_prefix + if state_prefix == "rebase-i" and config.show_numbers and rebase_done and rebase_total then + state_label = state_label .. " " .. rebase_done .. "/" .. rebase_total + end + end + + local unmerged_label = "" + local symbol = "" + + if unmerged_count and unmerged_count > 0 then + symbol = " " .. config.state_symbol + if config.show_numbers then + unmerged_label = tostring(unmerged_count) + end + end + + return { state_label .. symbol .. unmerged_label, theme.state_color } + end + return nil + end + + local function render_staged() + local staged = this.output.staged + + if staged then + local staged_label = config.staged_symbol + + if config.show_numbers then + local filtered_staged = staged:gsub("^[%s]*%b()[%s]*", "") + + local staged_count = 0 + for line in filtered_staged:gmatch("[^\r\n]+") do + if line:match("%S") then + staged_count = staged_count + 1 + end + end + + staged_label = staged_label .. staged_count + end + + return { staged_label, theme.staged_color } + end + + return nil + end + + local function render_unstaged() + local unstaged = this.output.unstaged + + if unstaged then + local unstaged_label = config.unstaged_symbol + + if config.show_numbers then + local filtered_unstaged = unstaged:gsub("^[%s]*%b()[\r\n]*", ""):gsub("^[%s]*%b()[\r\n]*", "") + + local unstaged_count = 0 + for line in filtered_unstaged:gmatch("[^\r\n]+") do + if line:match("%S") then + unstaged_count = unstaged_count + 1 + end + end + + unstaged_label = unstaged_label .. unstaged_count + end + + return { unstaged_label, theme.unstaged_color } + end + + return nil + end + + local function render_untracked() + local untracked = this.output.untracked + + if untracked then + local untracked_label = config.untracked_symbol + + if config.show_numbers then + local filtered_untracked = untracked:gsub("^[%s]*%b()[\r\n]*", "") + local untracked_count = 0 + + for line in filtered_untracked:gmatch("[^\r\n]+") do + if line:match("%S") then + untracked_count = untracked_count + 1 + end + end + + untracked_label = untracked_label .. untracked_count + end + + return { untracked_label, theme.untracked_color } + end + + return nil + end + + function Yatline.coloreds.get:githead() + if not this.output then + return nil + end + + local fn_table = { + branch = render_branch, + remote_branch = render_remote_branch, + tag = render_tag, + commit = render_commit, + behind_ahead_remote = render_behind_ahead_remote, + stashes = render_stashes, + state = render_state, + staged = render_staged, + unstaged = render_unstaged, + untracked = render_untracked, + } + + local head = {} + + for _, key in ipairs(config.order) do + local fn = fn_table[key] + local is_shown = config["show_" .. key] + + if fn and is_shown then + local value = fn() + if value then + if #head ~= 0 and head[#head][1] ~= " " and head[#head][2] ~= "black" then + table.insert(head, { " ", "black" }) + end + if type(value[1]) == "table" then + table.move(value, 1, #value, #head + 1, head) + else + table.insert(head, value) + end + end + end + end + + if #head ~= 0 then + return head + else + return nil + end + end + + local callback = function() + local cwd = cx.active.current.cwd + ya.emit("plugin", { + this._id, + ya.quote(tostring(cwd), true), + }) + end + + ps.sub("cd", callback) + ps.sub("rename", callback) + ps.sub("bulk", callback) + ps.sub("move", callback) + ps.sub("trash", callback) + ps.sub("delete", callback) + ps.sub("tab", callback) + end, + + entry = function(_, job) + local args = job.args or job + + ---@class GitStatusData + ---@field ahead_remote? integer + ---@field behind_remote? integer + ---@field branch? string + ---@field commit? string + ---@field rebase_done? integer + ---@field remote_branch? string + ---@field remote_repo? string + ---@field staged? string + ---@field stashes? integer + ---@field state_prefix? "bisect"|"cherry"|"merge"|"rebase-i"|"revert" + ---@field tag? string + ---@field unmerged_count? integer + ---@field unstaged? string + ---@field untracked? string + local data = {} + + --- @param status string + local get_behind_ahead_remote = function(status) + local diverged_ahead, diverged_behind = status:match("have (%d+) and (%d+) different") + if diverged_ahead and diverged_behind then + data.behind_remote = tonumber(diverged_behind) + data.ahead_remote = tonumber(diverged_ahead) + else + local behind_remote = status:match("behind %S+ by (%d+) commits?") + local ahead_remote = status:match("ahead of %S+ by (%d+) commits?") + + data.behind_remote = tonumber(behind_remote) + data.ahead_remote = tonumber(ahead_remote) + end + end + + --- @param status string + local get_branch = function(status) + data.branch = status:match("On branch (%S+)") + end + + --- @param status string + local get_stashes = function(status) + data.stashes = tonumber(status:match("Your stash currently has (%d+)")) + end + + --- @param status string + local get_state = function(status) + local unmerged = status:match("Unmerged paths:%s*(.-)%s*\n\n") + if unmerged then + local filtered_unmerged = unmerged:gsub("^[%s]*%b()[%s]*", ""):gsub("^[%s]*%b()[%s]*", "") + local count = 0 + for line in filtered_unmerged:gmatch("[^\r\n]+") do + if line:match("%S") then + count = count + 1 + end + end + data.unmerged_count = count + end + + if status:find("git merge") then + data.state_prefix = "merge" + elseif status:find("git cherry%-pick") then + data.state_prefix = "cherry" + elseif status:find("git rebase") then + data.state_prefix = "rebase-i" + local commands_done = status:match("%((%d+) commands? done%)") + if commands_done then + data.rebase_done = commands_done + if not data.unmerged_count then + data.rebase_total = commands_done + else + data.rebase_total = data.unmerged_count + end + end + elseif status:find("git revert") then + data.state_prefix = "revert" + elseif status:find("git bisect") then + data.state_prefix = "bisect" + end + end + + --- @param status string + local get_staged = function(status) + data.staged = status:match("Changes to be committed:%s*(.-)%s*\n\n") + end + + --- @param status string + local get_unstaged = function(status) + data.unstaged = status:match("Changes not staged for commit:%s*(.-)%s*\n\n") + end + + --- @param status string + local get_untracked = function(status) + data.untracked = status:match("Untracked files:%s*(.-)%s*\n\n") + end + + local get_status = function() + local cmd = Command("git") + :arg({ + "status", + "--ignore-submodules=dirty", + "--branch", + "--show-stash", + "--ahead-behind", + }) + :cwd(args[1]) + :env("LANGUAGE", "en_US.UTF-8") + :stdout(Command.PIPED) + local cmd_output = cmd:output() + + if cmd_output then + local status = cmd_output.stdout + + get_branch(status) + get_behind_ahead_remote(status) + get_stashes(status) + get_state(status) + get_staged(status) + get_unstaged(status) + get_untracked(status) + end + end + + local get_remote_branch = function() + local cmd = Command("git") + :arg({ + "rev-parse", + "--abbrev-ref", + "--symbolic-full-name", + "@{upstream}", + }) + :cwd(args[1]) + :env("LANGUAGE", "en_US.UTF-8") + :stdout(Command.PIPED) + local cmd_output = cmd:output() + + if cmd_output then + local remote_branch = cmd_output.stdout + + data.remote_branch = remote_branch:gsub("[\r\n]", ""):match("^[^/]+/(.+)") + data.remote_repo = remote_branch:gsub("[\r\n]", ""):match("^([^/]+)/") + end + end + + --- @param log string + local get_tag = function(log) + data.tag = log:match("tag: ([^, )]+)") + end + + --- @param log string + local get_commit = function(log) + data.commit = log:match("^commit%s+([a-f0-9]+)") + end + + local get_git_log = function() + local cmd = Command("git") + :arg({ "log", "--format=commit %h%d", "-n", "1" }) + :cwd(args[1]) + :env("LANGUAGE", "en_US.UTF-8") + :stdout(Command.PIPED) + local cmd_output = cmd:output() + + if cmd_output then + local log = cmd_output.stdout + + get_tag(log) + get_commit(log) + end + end + + get_status() + get_remote_branch() + get_git_log() + + save(args[1], data) + end, +} |
