|
local nsMiniFiles = vim.api.nvim_create_namespace("mini_files_git") |
|
local autocmd = vim.api.nvim_create_autocmd |
|
local _, MiniFiles = pcall(require, "mini.files") |
|
|
|
-- Cache for git status |
|
local gitStatusCache = {} |
|
local cacheTimeout = 2000 -- Cache timeout in milliseconds |
|
|
|
---@type table<string, {symbol: string, hlGroup: string}> |
|
---@param status string |
|
---@return string symbol, string hlGroup |
|
local function mapSymbols(status) |
|
local statusMap = { |
|
-- stylua: ignore start |
|
[" M"] = { symbol = "•", hlGroup = "GitSignsChange"}, -- Modified in the working directory |
|
["M "] = { symbol = "✹", hlGroup = "GitSignsChange"}, -- modified in index |
|
["MM"] = { symbol = "≠", hlGroup = "GitSignsChange"}, -- modified in both working tree and index |
|
["A "] = { symbol = "+", hlGroup = "GitSignsAdd" }, -- Added to the staging area, new file |
|
["AA"] = { symbol = "≈", hlGroup = "GitSignsAdd" }, -- file is added in both working tree and index |
|
["D "] = { symbol = "-", hlGroup = "GitSignsDelete"}, -- Deleted from the staging area |
|
["AM"] = { symbol = "⊕", hlGroup = "GitSignsChange"}, -- added in working tree, modified in index |
|
["AD"] = { symbol = "-•", hlGroup = "GitSignsChange"}, -- Added in the index and deleted in the working directory |
|
["R "] = { symbol = "→", hlGroup = "GitSignsChange"}, -- Renamed in the index |
|
["U "] = { symbol = "‖", hlGroup = "GitSignsChange"}, -- Unmerged path |
|
["UU"] = { symbol = "⇄", hlGroup = "GitSignsAdd" }, -- file is unmerged |
|
["UA"] = { symbol = "⊕", hlGroup = "GitSignsAdd" }, -- file is unmerged and added in working tree |
|
["??"] = { symbol = "?", hlGroup = "GitSignsDelete"}, -- Untracked files |
|
["!!"] = { symbol = "!", hlGroup = "GitSignsChange"}, -- Ignored files |
|
-- stylua: ignore end |
|
} |
|
|
|
local result = statusMap[status] |
|
or { symbol = "?", hlGroup = "NonText" } |
|
return result.symbol, result.hlGroup |
|
end |
|
|
|
---@param cwd string |
|
---@param callback function |
|
---@return nil |
|
local function fetchGitStatus(cwd, callback) |
|
local function on_exit(content) |
|
if content.code == 0 then |
|
callback(content.stdout) |
|
vim.g.content = content.stdout |
|
end |
|
end |
|
vim.system( |
|
{ "git", "status", "--ignored", "--porcelain" }, |
|
{ text = true, cwd = cwd }, |
|
on_exit |
|
) |
|
end |
|
|
|
---@param str string? |
|
local function escapePattern(str) |
|
return str:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") |
|
end |
|
|
|
---@param buf_id integer |
|
---@param gitStatusMap table |
|
---@return nil |
|
local function updateMiniWithGit(buf_id, gitStatusMap) |
|
vim.schedule(function() |
|
local nlines = vim.api.nvim_buf_line_count(buf_id) |
|
local cwd = vim.fs.root(buf_id, ".git") |
|
local escapedcwd = escapePattern(cwd) |
|
if vim.fn.has("win32") == 1 then |
|
escapedcwd = escapedcwd:gsub("\\", "/") |
|
end |
|
|
|
for i = 1, nlines do |
|
local entry = MiniFiles.get_fs_entry(buf_id, i) |
|
if not entry then |
|
break |
|
end |
|
local relativePath = entry.path:gsub("^" .. escapedcwd .. "/", "") |
|
local status = gitStatusMap[relativePath] |
|
|
|
if status then |
|
local symbol, hlGroup = mapSymbols(status) |
|
vim.api.nvim_buf_set_extmark(buf_id, nsMiniFiles, i - 1, 0, { |
|
-- NOTE: if you want the signs on the right uncomment those and comment |
|
-- the 3 lines after |
|
-- virt_text = { { symbol, hlGroup } }, |
|
-- virt_text_pos = "right_align", |
|
sign_text = symbol, |
|
sign_hl_group = hlGroup, |
|
priority = 2, |
|
}) |
|
else |
|
end |
|
end |
|
end) |
|
end |
|
|
|
|
|
-- Thanks for the idea of gettings https://github.com/refractalize/oil-git-status.nvim signs for dirs |
|
---@param content string |
|
---@return table |
|
local function parseGitStatus(content) |
|
local gitStatusMap = {} |
|
-- lua match is faster than vim.split (in my experience ) |
|
for line in content:gmatch("[^\r\n]+") do |
|
local status, filePath = string.match(line, "^(..)%s+(.*)") |
|
-- Split the file path into parts |
|
local parts = {} |
|
for part in filePath:gmatch("[^/]+") do |
|
table.insert(parts, part) |
|
end |
|
-- Start with the root directory |
|
local currentKey = "" |
|
for i, part in ipairs(parts) do |
|
if i > 1 then |
|
-- Concatenate parts with a separator to create a unique key |
|
currentKey = currentKey .. "/" .. part |
|
else |
|
currentKey = part |
|
end |
|
-- If it's the last part, it's a file, so add it with its status |
|
if i == #parts then |
|
gitStatusMap[currentKey] = status |
|
else |
|
-- If it's not the last part, it's a directory. Check if it exists, if not, add it. |
|
if not gitStatusMap[currentKey] then |
|
gitStatusMap[currentKey] = status |
|
end |
|
end |
|
end |
|
end |
|
return gitStatusMap |
|
end |
|
|
|
---@param buf_id integer |
|
---@return nil |
|
local function updateGitStatus(buf_id) |
|
if not vim.fs.root(vim.uv.cwd(), ".git") then |
|
return |
|
end |
|
|
|
local cwd = vim.fn.expand("%:p:h") |
|
local currentTime = os.time() |
|
if |
|
gitStatusCache[cwd] |
|
and currentTime - gitStatusCache[cwd].time < cacheTimeout |
|
then |
|
updateMiniWithGit(buf_id, gitStatusCache[cwd].statusMap) |
|
else |
|
fetchGitStatus(cwd, function(content) |
|
local gitStatusMap = parseGitStatus(content) |
|
gitStatusCache[cwd] = { |
|
time = currentTime, |
|
statusMap = gitStatusMap, |
|
} |
|
updateMiniWithGit(buf_id, gitStatusMap) |
|
end) |
|
end |
|
end |
|
|
|
---@return nil |
|
local function clearCache() |
|
gitStatusCache = {} |
|
end |
|
|
|
local function augroup(name) |
|
return vim.api.nvim_create_augroup( |
|
"MiniFiles_" .. name, |
|
{ clear = true } |
|
) |
|
end |
|
|
|
autocmd("User", { |
|
group = augroup("start"), |
|
pattern = "MiniFilesExplorerOpen", |
|
-- pattern = { "minifiles" }, |
|
callback = function() |
|
local bufnr = vim.api.nvim_get_current_buf() |
|
updateGitStatus(bufnr) |
|
end, |
|
}) |
|
|
|
autocmd("User", { |
|
group = augroup("close"), |
|
pattern = "MiniFilesExplorerClose", |
|
callback = function() |
|
clearCache() |
|
end, |
|
}) |
|
|
|
autocmd("User", { |
|
group = augroup("update"), |
|
pattern = "MiniFilesBufferUpdate", |
|
callback = function(sii) |
|
local bufnr = sii.data.buf_id |
|
local cwd = vim.fn.expand("%:p:h") |
|
if gitStatusCache[cwd] then |
|
updateMiniWithGit(bufnr, gitStatusCache[cwd].statusMap) |
|
end |
|
end, |
|
}) |
|
|