-
Notifications
You must be signed in to change notification settings - Fork 10
/
init.lua
211 lines (188 loc) · 4.89 KB
/
init.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
local WIN = ya.target_family() == "windows"
local PATS = {
{ "[MT]", 6 }, -- Modified
{ "[AC]", 5 }, -- Added
{ "?$", 4 }, -- Untracked
{ "!$", 3 }, -- Ignored
{ "D", 2 }, -- Deleted
{ "U", 1 }, -- Updated
{ "[AD][AD]", 1 }, -- Updated
}
local function match(line)
local signs = line:sub(1, 2)
for _, p in ipairs(PATS) do
local path
if signs:find(p[1]) then
path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4)
path = WIN and path:gsub("/", "\\") or path
end
if not path then
elseif path:find("[/\\]$") then
return p[2] == 3 and 30 or p[2], path:sub(1, -2)
else
return p[2], path
end
end
end
local function root(cwd)
local is_worktree = function(url)
local file, head = io.open(tostring(url)), nil
if file then
head = file:read(8)
file:close()
end
return head == "gitdir: "
end
repeat
local next = cwd:join(".git")
local cha = fs.cha(next)
if cha and (cha.is_dir or is_worktree(next)) then
return tostring(cwd)
end
cwd = cwd:parent()
until not cwd
end
local function bubble_up(changed)
local new, empty = {}, Url("")
for k, v in pairs(changed) do
if v ~= 3 and v ~= 30 then
local url = Url(k):parent()
while url and url ~= empty do
local s = tostring(url)
new[s] = (new[s] or 0) > v and new[s] or v
url = url:parent()
end
end
end
return new
end
local function propagate_down(ignored, cwd, repo)
local new, rel = {}, cwd:strip_prefix(repo)
for k, v in pairs(ignored) do
if v == 30 then
if rel:starts_with(k) then
new[tostring(repo:join(rel))] = 30
elseif cwd == repo:join(k):parent() then
new[k] = 3
end
end
end
return new
end
local add = ya.sync(function(st, cwd, repo, changed)
st.dirs[cwd] = repo
st.repos[repo] = st.repos[repo] or {}
for k, v in pairs(changed) do
if v == 0 then
st.repos[repo][k] = nil
elseif v == 30 then
st.dirs[k] = ""
else
st.repos[repo][k] = v
end
end
ya.render()
end)
local remove = ya.sync(function(st, cwd)
local dir = st.dirs[cwd]
if not dir then
return
end
ya.render()
st.dirs[cwd] = nil
if not st.repos[dir] then
return
end
for _, r in pairs(st.dirs) do
if r == dir then
return
end
end
st.repos[dir] = nil
end)
local function setup(st, opts)
st.dirs = {}
st.repos = {}
opts = opts or {}
opts.order = opts.order or 1500
-- Chosen by ChatGPT fairly, PRs are welcome to adjust them
local t = THEME.git or {}
local styles = {
[6] = t.modified and ui.Style(t.modified) or ui.Style():fg("#ffa500"),
[5] = t.added and ui.Style(t.added) or ui.Style():fg("#32cd32"),
[4] = t.untracked and ui.Style(t.untracked) or ui.Style():fg("#a9a9a9"),
[3] = t.ignored and ui.Style(t.ignored) or ui.Style():fg("#696969"),
[2] = t.deleted and ui.Style(t.deleted) or ui.Style():fg("#ff4500"),
[1] = t.updated and ui.Style(t.updated) or ui.Style():fg("#1e90ff"),
}
local signs = {
[6] = t.modified_sign and t.modified_sign or "",
[5] = t.added_sign and t.added_sign or "",
[4] = t.untracked_sign and t.untracked_sign or "",
[3] = t.ignored_sign and t.ignored_sign or "",
[2] = t.deleted_sign and t.deleted_sign or "",
[1] = t.updated_sign and t.updated_sign or "U",
}
Linemode:children_add(function(self)
local url = self._file.url
local dir = st.dirs[tostring(url:parent())]
local change
if dir then
change = dir == "" and 3 or st.repos[dir][tostring(url):sub(#dir + 2)]
end
if not change or signs[change] == "" then
return ui.Line("")
elseif self._file:is_hovered() then
return ui.Line { ui.Span(" "), ui.Span(signs[change]) }
else
return ui.Line { ui.Span(" "), ui.Span(signs[change]):style(styles[change]) }
end
end, opts.order)
end
local function fetch(self, job)
-- TODO: remove this once Yazi 0.4 is released
job = job or self
local cwd = job.files[1].url:parent()
local repo = root(cwd)
if not repo then
remove(tostring(cwd))
return 1
end
local paths = {}
for _, f in ipairs(job.files) do
paths[#paths + 1] = tostring(f.url)
end
-- stylua: ignore
local output, err = Command("git")
:cwd(tostring(cwd))
:args({ "--no-optional-locks", "-c", "core.quotePath=", "status", "--porcelain", "-unormal", "--no-renames", "--ignored=matching" })
:args(paths)
:stdout(Command.PIPED)
:output()
if not output then
ya.err("Cannot spawn git command, error code " .. tostring(err))
return 0
end
local changed, ignored = {}, {}
for line in output.stdout:gmatch("[^\r\n]+") do
local sign, path = match(line)
if sign == 30 then
ignored[path] = sign
else
changed[path] = sign
end
end
if job.files[1].cha.is_dir then
ya.dict_merge(changed, bubble_up(changed))
ya.dict_merge(changed, propagate_down(ignored, cwd, Url(repo)))
else
ya.dict_merge(changed, propagate_down(ignored, cwd, Url(repo)))
end
for _, p in ipairs(paths) do
local s = p:sub(#repo + 2)
changed[s] = changed[s] or 0
end
add(tostring(cwd), repo, changed)
return 3
end
return { setup = setup, fetch = fetch }