config.nvim

NeoVim config
git clone git://popovic.xyz/nvim.config.git/
Log | Files | Refs

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