Skip to content

Instantly share code, notes, and snippets.

@ChHaeni
Last active July 15, 2024 06:03
Show Gist options
  • Save ChHaeni/b15938c2f41b178f476b1bc4cecc0271 to your computer and use it in GitHub Desktop.
Save ChHaeni/b15938c2f41b178f476b1bc4cecc0271 to your computer and use it in GitHub Desktop.

Integrating git status in lualine

Introduction

This gist shows a possible way how to integrate the status of the buffer related repository into the neovim plugin lualine.

The git status is indicated in the lower left corner of the screenshot, indicating (1 commit ahead, 1 staged, 1 modified and 1 untracked file):

I just started using lua, so the way how the code below works can certainly be improved.

Implementation

Helper functions

-- helper function to loop over string lines
-- copied from https://stackoverflow.com/a/19329565
local function iterlines(s)
        if s:sub(-1)~="\n" then s=s.."\n" end
        return s:gmatch("(.-)\n")
end

-- current working directory
local function pwd()
    local pwd = vim.fn['getcwd']()
    pwd = pwd:gsub("/home/christoph", "~")
    pwd = pwd:gsub("/home/hac5", "~")
    return pwd
end

-- current vimtux session
local function vimtux()
    -- get actual window name
    local cmd = "tmux display-message -p -t '" .. vim.b.vimtux['session'] ..
        ':' .. vim.b.vimtux['window'] .. '.' .. vim.b.vimtux['pane'] .. "' -F '#W#F' 2>&1"
    local handle = assert(io.popen(cmd, 'r'), '')
    -- output contains empty line at end
    local window_name = assert(handle:read('*a'))
    -- close io
    handle:close()
    -- remove newline
    if window_name:sub(-1)=="\n" then window_name=window_name:sub(1, -2) end
    if window_name:find("can.t find") then
        return "s:" .. vim.b.vimtux['session'] .. " w:" .. vim.b.vimtux['window'] .. " p:" .. vim.b.vimtux['pane'] .. " -> " .. window_name .. " !!!"
    end
    -- otherwise add window name
    return "s:" .. vim.b.vimtux['session'] .. " w:" .. vim.b.vimtux['window'] .. "|" .. window_name .. " p:" .. vim.b.vimtux['pane']
end

-- current vim session
local function vim_session()
    local vs = vim.fn['xolox#session#find_current_session']()
    if (vs == nil or vs == '') then
        return vs
    else 
        return vs .. ":"
    end
end

-- write file_exists
function check_file_path()
    -- terminal
    if vim.bo.buftype == 'terminal' then
        return
    end
    local file = vim.fn.expand("%:p")
    -- fix fugitive etc.
    git_state = {'', '', '', ''}
    if file:find("^[%w-]+://") ~= nil then
        git_state[1] = ' ' .. file:gsub("^([%w-]+)://.*", "%1") .. ' '
        file = file:gsub("^[%w-]+://", "")
    end
    vim.b.git_state = git_state
    -- check file exists?
    ok, err, code = os.rename(file, file)
    -- check dir
    if ok or code == 13 then
        -- write buffer variable
        vim.b["file_exists"] = 1
    else
        vim.b["file_exists"] = 0
    end
end

-- find directory
function find_dir(d)
    -- return if root
    if d == '/' then
        return d
    end
    -- initialize git_state variable
    if vim.b.git_state == nil then
        vim.b.git_state = {'', '', '', ''}
    end
    -- fix terminal
    if d:find("term://") ~= nil then
        return "/tmp"
    end
    -- fix fzf
    if d:find("/tmp/.*FZF") ~= nil then
        return "/tmp"
    end
    -- fix fugitive etc.
    if d:find("^[%w-]+://") ~= nil then
        vim.b.git_state[1] = ' ' .. d:gsub("^([%w-]+)://.*", "%1") .. ' '
        d = d:gsub("^[%w-]+://", "")
    end
    -- check renaming
    local ok, err, code = os.rename(d, d)
    if not ok then
        if code ~= 2 then
            -- all other than not existing
            return d
        end
        -- not existing
        local newd = d:gsub("(.*/)[%w._-]+/?$", "%1")
        return find_dir(newd)
    end
    -- d ok
    return d
end

-- check if file exists
local function file_exists()
    return vim.b["file_exists"] == 1
end

-- get git status
local function git_status()
    vim.b.git_state = {'', '', ''}
    -- get & check file directory
    file_dir = find_dir(vim.fn.expand("%:p:h"))
    -- check fugitive etc.
    if vim.b.git_state[1] ~= "" then
        return 'd'
    end
    -- capture git status call
    local cmd = "git -C " .. file_dir .. " status --porcelain -b 2> /dev/null"
    local handle = assert(io.popen(cmd, 'r'), '')
    -- output contains empty line at end (removed by iterlines)
    local output = assert(handle:read('*a'))
    -- close io
    handle:close()

    --  (| not in output, marks start and end of first two chars for readability)
    -- first line, head:
    --   ## master...origin/master => up to date => green
    --   ## master...origin/master [ahead 1] => ahead of origin => green + arrows up ↑
    --   ## master...origin/master [behind 1] => behind origin => green + arrows down ↓
    --   ## master...origin/master [ahead 1, behind 1] => both
    --   ## HEAD (no branch) => HEAD detached => purple
    -- following lines, files:
    --   |??| => untracked file
    --   | D| => deleted = modified
    --   |R | => renamed = staged
    --   etc. first char staged, second char modified
    
    -- iterate over lines
    -- git_state is array with entries branch/staged/modified/untracked
    local git_state = {'', '', '', ''}
    -- branch coloring: 'o': up to date with origin; 'd': head detached; 'm': not up to date with origin
    local branch_col = 'o'

    -- check if git repo
    if output == '' then
        -- not a git repo
        -- save to variable
        vim.b.git_state = git_state
        -- exit
        return branch_col
    end

    -- get line iterator
    local line_iter = iterlines(output)

    -- process first line (HEAD)
    local line = line_iter()
    if line:find("%(no branch%)") ~= nil then
        -- detached head
        branch_col = 'd'
    else
        -- on branch
        local ahead = line:gsub(".*ahead (%d+).*", "%1")
        local behind = line:gsub(".*behind (%d+).*", "%1")
        -- convert non-numeric to nil
        ahead = tonumber(ahead)
        behind = tonumber(behind)
        if behind ~= nil then
            git_state[1] = '' .. tostring(behind) .. ' '
        end
        if ahead ~= nil then
            git_state[1] = git_state[1] .. '' .. tostring(ahead) .. ' '
        end
    end

    -- loop over residual lines (files) &
    -- store number of files
    local git_num = {0, 0, 0}
    for line in line_iter do
        branch_col = 'm'
        -- get first char
        local first = line:gsub("^(.).*", "%1")
        if first == '?' then
            -- untracked
            git_num[3] = git_num[3] + 1
        elseif first ~= ' ' then
            -- staged
            git_num[1] = git_num[1] + 1
        end
        -- get second char
        local second = line:gsub("^.(.).*", "%1")
        if second == 'M' or second == 'D' then
            -- modified or deleted
            git_num[2] = git_num[2] + 1
        end
    end

    -- build output string
    if git_num[1] ~= 0 then
        git_state[2] = '' .. git_num[1]
    end
    if git_num[2] ~= 0 then
        git_state[3] = '+ ' .. git_num[2]
    end
    if git_num[3] ~= 0 then
        git_state[4] = '' .. git_num[3]
    end
    
    -- save to variable
    vim.b.git_state = git_state
    
    return branch_col
end

lualine config

lualine_b = {
    {
        'branch',
        color = 
            function(section)
                local gs = git_status()
                if gs == 'd' then
                    return { fg = '#916BDD' }
                elseif gs ~= 'm' then
                    return { fg = '#769945' }
                end
            end
    }, 
    {
        -- head status
        "vim.b.git_state[1]",
        color = function(section)
            if vim.b.git_state[1]:find("^ %w+ $") ~= nil then
                return { fg = '#F49B55' }
            end
        end,
        padding = { left = 0, right = 0 }
    },
    { 
        -- staged files
        "vim.b.git_state[2]",
        color = { fg = '#769945' },
        padding = { left = 0, right = 1 }
    },
    {
        -- modified files
        "vim.b.git_state[3]",
        color = { fg = '#D75F00' },
        padding = { left = 0, right = 1 }
    },
    {
        -- untracked files
        "vim.b.git_state[4]",
        color = { fg = '#D99809' },
        padding = { left = 0, right = 1 }
    },
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment