include plugins

This commit is contained in:
Simon Belmont 2025-05-08 20:48:25 -04:00
parent ae61234858
commit cd81d7b2b4
21 changed files with 1201 additions and 0 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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 }