status.lua (3898B)
1 ---@diagnostic disable: undefined-global 2 3 local M = {} 4 5 local function buf_dir(bufnr) 6 local bt = vim.bo[bufnr].buftype 7 if bt ~= nil and bt ~= '' then 8 return nil 9 end 10 11 local name = vim.api.nvim_buf_get_name(bufnr) 12 if name == '' or name:find('Undo tree', 1, true) then 13 return nil 14 end 15 16 -- Exclude non-filesystem buffers (URIs like term://, fugitive://, oil://, etc.) 17 if name:match('^%a[%w+.-]*://') then 18 return nil 19 end 20 21 return vim.fn.fnamemodify(name, ':p:h') 22 end 23 24 local function set_git(bufnr, info) 25 local function apply() 26 vim.b[bufnr].status_git = info 27 vim.cmd('redrawstatus') 28 end 29 30 if vim.in_fast_event and vim.in_fast_event() then 31 vim.schedule(apply) 32 else 33 apply() 34 end 35 end 36 37 local function parse_git_status(stdout) 38 local info = { 39 branch = '', 40 ahead = 0, 41 behind = 0, 42 staged = 0, 43 unstaged = 0, 44 untracked = 0, 45 } 46 47 local head, oid 48 49 for line in (stdout or ''):gmatch('[^\n]+') do 50 head = head or line:match('^# branch%.head%s+(.+)$') 51 oid = oid or line:match('^# branch%.oid%s+([0-9a-f]+)$') 52 53 local a = line:match('^# branch%.ab%s+%+(%d+)%s+%-(%d+)$') 54 if a then 55 info.ahead, info.behind = tonumber(line:match('%+(%d+)')) or 0, tonumber(line:match('%-(%d+)')) or 0 56 end 57 58 local xy = line:match('^[12]%s+(..)%s') 59 if xy then 60 local x, y = xy:sub(1, 1), xy:sub(2, 2) 61 if x ~= '.' then 62 info.staged = info.staged + 1 63 end 64 if y ~= '.' then 65 info.unstaged = info.unstaged + 1 66 end 67 elseif line:match('^%?%s') then 68 info.untracked = info.untracked + 1 69 end 70 end 71 72 if head and head ~= '' then 73 if head == '(detached)' then 74 if oid and oid ~= '' and oid ~= '(initial)' then 75 info.branch = oid:sub(1, 7) 76 end 77 else 78 info.branch = head 79 end 80 end 81 82 return info 83 end 84 85 function M.update_git_branch(bufnr) 86 bufnr = bufnr or vim.api.nvim_get_current_buf() 87 local dir = buf_dir(bufnr) 88 if not dir then 89 return set_git(bufnr, { branch = '' }) 90 end 91 92 local function on_status(code, stdout) 93 if code ~= 0 then 94 return set_git(bufnr, { branch = '' }) 95 end 96 local info = parse_git_status(stdout) 97 if not info.branch or info.branch == '' then 98 return set_git(bufnr, { branch = '' }) 99 end 100 set_git(bufnr, info) 101 end 102 103 if vim.system then 104 vim.system({ 'git', 'status', '--porcelain=2', '-b' }, { cwd = dir, text = true }, function(obj) 105 on_status(obj.code, obj.stdout) 106 end) 107 else 108 local out = vim.fn.system({ 'git', '-C', dir, 'status', '--porcelain=2', '-b' }) 109 on_status(vim.v.shell_error, out) 110 end 111 end 112 113 function M.git_component() 114 local info = vim.b.status_git 115 if info == nil then 116 M.update_git_branch(0) 117 return '' 118 end 119 120 local branch = info.branch or '' 121 if branch == '' then 122 return '' 123 end 124 125 local parts = { string.format(' %s', branch) } 126 127 if (info.ahead or 0) > 0 then 128 parts[#parts + 1] = string.format('↑%d', info.ahead) 129 end 130 if (info.behind or 0) > 0 then 131 parts[#parts + 1] = string.format('↓%d', info.behind) 132 end 133 if (info.staged or 0) > 0 then 134 parts[#parts + 1] = string.format('+%d', info.staged) 135 end 136 if (info.unstaged or 0) > 0 then 137 parts[#parts + 1] = string.format('~%d', info.unstaged) 138 end 139 if (info.untracked or 0) > 0 then 140 parts[#parts + 1] = string.format('?%d', info.untracked) 141 end 142 143 return table.concat(parts, ' ') 144 end 145 146 function M.file_component() 147 local name = vim.fn.expand('%:~:.') 148 if name == '' then 149 return '[No Name]' 150 end 151 152 local max = math.floor((vim.o.columns or 120) * 0.45) 153 if #name > max then 154 name = vim.fn.pathshorten(name) 155 end 156 157 return name 158 end 159 160 vim.o.statusline = table.concat({ 161 "%{v:lua.require'config.status'.git_component()}", 162 " - ", 163 "%{v:lua.require'config.status'.file_component()}", 164 " %m", 165 "%=", 166 "%y", 167 " · ", 168 "%l:%c", 169 }) 170 171 return M