diff --git a/lua/config/minifiles.lua b/lua/config/minifiles.lua new file mode 100644 index 0000000..2577992 --- /dev/null +++ b/lua/config/minifiles.lua @@ -0,0 +1,196 @@ +-- ╭─────────────────────────────────────────────────────────╮ +-- │ Setup Git Signs for Mini.Files │ +-- ╰─────────────────────────────────────────────────────────╯ + +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 +---@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) + ---@diagnostic disable-next-line: need-check-nil + 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, { + 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) + ---@diagnostic disable-next-line: param-type-mismatch + 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, +}) diff --git a/lua/data/types.lua b/lua/data/types.lua index fa88b93..754bf99 100644 --- a/lua/data/types.lua +++ b/lua/data/types.lua @@ -410,6 +410,9 @@ M.minifiles = { LazyVim.lsp.on_rename(event.data.from, event.data.to) end, }) + + -- include git signs + require("config.minifiles") end, }