implemted oil compatibility

This commit is contained in:
2026-09-18 15:03:27 +02:00
parent 75eae3f174
commit d41c23b9aa
2 changed files with 364 additions and 1 deletions
@@ -0,0 +1,363 @@
-- git highlighting for the mini.files
-- unfortunately this is a mountain of code...
local M = {}
local uv = vim.uv or vim.loop
local cache_ttl_ms = 1000
local status_cache = {}
local git_root_cache = {}
local known_git_roots = {}
local function is_path_inside(path, root)
return path == root or vim.startswith(path, root .. "/")
end
local function new_status_node()
return {
children = {},
status = nil,
priority = 0,
is_dir_ignored = false,
is_dir_untracked = false,
}
end
local function is_conflict(status)
if #status < 2 then
return false
end
local index_status = status:sub(1, 1)
local worktree_status = status:sub(2, 2)
return index_status == "U"
or worktree_status == "U"
or status == "AA"
or status == "DD"
end
local function status_priority(status)
if #status < 2 then
return 0
end
if is_conflict(status) then
return 8
end
if status == "!!" then
return 1
end
if status == "??" then
return 2
end
local index_status = status:sub(1, 1)
local worktree_status = status:sub(2, 2)
if index_status == "M" then
return 7
end
if index_status == "D" or worktree_status == "D" then
return 5
end
if index_status == "A" then
return 4
end
if index_status == "R" or index_status == "C" then
return 3
end
if worktree_status == "M" then
return 6
end
return 0
end
local function status_highlight(status)
if not status then
return nil
end
if status == "!!" then
return "Comment"
end
if is_conflict(status) then
return "MiniDiffSignDelete"
end
if status == "??" or status:sub(1, 1) == "A" then
return "MiniDiffSignAdd"
end
if status:sub(1, 1) == "D" or status:sub(2, 2) == "D" then
return "MiniDiffSignDelete"
end
if status:sub(1, 1) == "M"
or status:sub(1, 1) == "R"
or status:sub(1, 1) == "C"
or status:sub(2, 2) == "M"
then
return "MiniDiffSignChange"
end
return nil
end
local function insert_status(root, relative_path, status, is_directory)
local priority = status_priority(status)
if priority == 0 then
return
end
local node = root
local segments = vim.split(relative_path, "/", { plain = true, trimempty = true })
for index, segment in ipairs(segments) do
node.children[segment] = node.children[segment] or new_status_node()
node = node.children[segment]
if index == #segments then
if priority > node.priority then
node.status = status
node.priority = priority
end
if is_directory and status == "!!" then
node.is_dir_ignored = true
end
if is_directory and status == "??" then
node.is_dir_untracked = true
end
end
end
end
local function subtree_status(node)
local best_status = node.status
local best_priority = node.priority
for _, child in pairs(node.children) do
local child_status, child_priority = subtree_status(child)
if child_priority > best_priority then
best_status = child_status
best_priority = child_priority
end
end
return best_status, best_priority
end
local function parse_status(output, root)
local data = {
root = root,
statuses = {},
trie = new_status_node(),
timestamp = uv.now(),
}
local records = vim.split(output, "\0", { plain = true, trimempty = true })
local record_index = 1
while record_index <= #records do
local record = records[record_index]
if #record >= 4 then
local status = record:sub(1, 2)
local relative_path = record:sub(4)
-- With -z, rename/copy output contains the new path followed by
-- the old path. Oil highlights the new path only.
if status:sub(1, 1) == "R" or status:sub(1, 1) == "C" then
record_index = record_index + 1
end
local is_directory = relative_path:sub(-1) == "/"
relative_path = relative_path:gsub("^%./", ""):gsub("/$", "")
if relative_path ~= "" then
local absolute_path = vim.fs.normalize(vim.fs.joinpath(root, relative_path))
data.statuses[absolute_path] = status
insert_status(data.trie, relative_path, status, is_directory)
end
end
record_index = record_index + 1
end
return data
end
local function find_cached_root(path)
local result
for root in pairs(known_git_roots) do
if is_path_inside(path, root) and (result == nil or #root > #result) then
result = root
end
end
return result
end
local function find_git_root(path)
local cached_root = find_cached_root(path)
if cached_root ~= nil then
return cached_root
end
local cached = git_root_cache[path]
if cached ~= nil then
return cached or nil
end
local result = vim.system({ "git", "-C", path, "rev-parse", "--show-toplevel" }, { text = true }):wait()
if result.code ~= 0 then
git_root_cache[path] = false
return nil
end
local root = vim.trim(result.stdout)
if root == "" then
git_root_cache[path] = false
return nil
end
root = vim.fs.normalize(root)
git_root_cache[path] = root
known_git_roots[root] = true
return root
end
local function get_status_data(fs_entry)
local path = fs_entry.path
if fs_entry.fs_type ~= "directory" then
path = vim.fs.dirname(path)
end
path = vim.fs.normalize(path)
local root = find_git_root(path)
if root == nil then
return nil
end
local cached = status_cache[root]
if cached ~= nil and uv.now() - cached.timestamp < cache_ttl_ms then
return cached
end
local result = vim.system({
"git",
"-C",
root,
"--no-optional-locks",
"status",
"--porcelain",
"--ignored",
"-z",
}, { text = true }):wait()
if result.code ~= 0 then
status_cache[root] = parse_status("", root)
return status_cache[root]
end
local data = parse_status(result.stdout, root)
status_cache[root] = data
return data
end
local function lookup_status(data, fs_entry)
local path = vim.fs.normalize(fs_entry.path)
local direct_status = data.statuses[path]
if fs_entry.fs_type ~= "directory" and direct_status ~= nil then
return direct_status
end
if not is_path_inside(path, data.root) then
return nil
end
local relative_path = path:sub(#data.root + 2)
local node = data.trie
for _, segment in ipairs(vim.split(relative_path, "/", { plain = true, trimempty = true })) do
if node.is_dir_ignored then
return "!!"
end
if node.is_dir_untracked then
return "??"
end
node = node.children[segment]
if node == nil then
return nil
end
end
if node.is_dir_ignored then
return "!!"
end
if node.is_dir_untracked then
return "??"
end
if next(node.children) == nil then
return node.status
end
local status = subtree_status(node)
-- Match the Oil configuration: an ignored descendant does not grey its
-- parent directory unless Git reported that directory as ignored.
if fs_entry.fs_type == "directory" and status == "!!" then
return nil
end
return status
end
function M.filter(fs_entry)
return fs_entry.name ~= ".git"
end
function M.highlight(fs_entry)
local data = get_status_data(fs_entry)
local status = data == nil and nil or lookup_status(data, fs_entry)
return status_highlight(status)
or (fs_entry.fs_type == "directory" and "MiniFilesDirectory" or "MiniFilesFile")
end
function M.invalidate()
status_cache = {}
git_root_cache = {}
known_git_roots = {}
end
local function refresh()
M.invalidate()
if _G.MiniFiles == nil or MiniFiles.get_explorer_state() == nil then
return
end
vim.schedule(function()
if MiniFiles.get_explorer_state() ~= nil then
MiniFiles.refresh()
end
end)
end
function M.setup()
local group = vim.api.nvim_create_augroup("romanzy_mini_files_git", { clear = true })
vim.api.nvim_create_autocmd({ "BufWritePost", "FocusGained", "TermClose" }, {
group = group,
callback = refresh,
})
vim.api.nvim_create_autocmd("User", {
group = group,
pattern = {
"GitSignsUpdate",
"LazyGitClosed",
"MiniFilesActionCreate",
"MiniFilesActionDelete",
"MiniFilesActionRename",
"MiniFilesActionCopy",
"MiniFilesActionMove",
},
callback = refresh,
})
return {
content = {
filter = M.filter,
highlight = M.highlight,
},
}
end
return M
@@ -43,7 +43,7 @@ clue.setup({
icons.setup({}) icons.setup({})
files.setup({}) files.setup(require("romanzy.custom.mini_files_git").setup())
git.setup({}) git.setup({})