local M = {} -- Highlighting Shortcut local hi_pattern = '%%#%s#%s%%*' M.hi_pattern = hi_pattern -- Use icons? M.use_icons = false -- Utilities local utils = {} -- The width of the statusline. utils.linewidth = function () if vim.o.laststatus == 3 then return vim.o.columns else return vim.api.nvim_win_get_width(0) end end utils.component_takes_percentage = function (comp_width, percentage) return comp_width > (percentage/100) * utils.linewidth() end M.utils = utils -- Pre-made Statusline Components M.components = {} M.components.mode = function (args) args = args or {} local before = args.before or "" local after = args.after or "" -- Convert mode to string local mode_to_str = { ['n'] = 'NORMAL', ['no'] = 'OP-PENDING', ['nov'] = 'OP-PENDING', ['noV'] = 'OP-PENDING', ['no\22'] = 'OP-PENDING', ['niI'] = 'NORMAL', ['niR'] = 'NORMAL', ['niV'] = 'NORMAL', ['nt'] = 'NORMAL', ['ntT'] = 'NORMAL', ['v'] = 'VISUAL', ['vs'] = 'VISUAL', ['V'] = 'VISUAL', ['Vs'] = 'VISUAL', ['\22'] = 'VISUAL', ['\22s'] = 'VISUAL', ['s'] = 'SELECT', ['S'] = 'SELECT', ['\19'] = 'SELECT', ['i'] = 'INSERT', ['ic'] = 'INSERT', ['ix'] = 'INSERT', ['R'] = 'REPLACE', ['Rc'] = 'REPLACE', ['Rx'] = 'REPLACE', ['Rv'] = 'VIRT REPLACE', ['Rvc'] = 'VIRT REPLACE', ['Rvx'] = 'VIRT REPLACE', ['c'] = 'COMMAND', ['cv'] = 'VIM EX', ['ce'] = 'EX', ['r'] = 'PROMPT', ['rm'] = 'MORE', ['r?'] = 'CONFIRM', ['!'] = 'SHELL', ['t'] = 'TERMINAL', } -- Get the respective string to display. local mode = mode_to_str[vim.api.nvim_get_mode().mode] or 'UNKNOWN' -- Set the highlight group and text. local mode_text = "?" local hl = 'DiagnosticWarn' if mode:find 'NORMAL' then hl = 'DiagnosticInfo' mode_text = "N" elseif mode:find 'PENDING' then hl = 'DiagnosticInfo' mode_text = "P" elseif mode:find 'VISUAL' then hl = 'DiagnosticHint' mode_text = "V" elseif mode:find 'REPLACE' then hl = 'DiagnosticError' mode_text = "R" elseif mode:find 'INSERT' or mode:find 'SELECT' then hl = 'DiagnosticOk' mode_text = "I" elseif mode:find 'COMMAND' or mode:find 'TERMINAL' or mode:find 'EX' then hl = 'DiagnosticHint' mode_text = "C" end -- Construct the component. return hi_pattern:format(hl, string.format(before..'%s'..after, mode_text)) end M.components.location = function (args) args = args or {} local before = args.before or "" local after = args.after or "" return before.."%(%l/%L%): %c"..after end M.components.filename = function (args) args = args or {} local before = args.before or "" local after = args.after or "" local shorten_percentage = args.shorten_percentage or 70 -- Get filename local filename = vim.fn.expand("%t") -- Shorten if too large if utils.component_takes_percentage(#filename, shorten_percentage) then filename = vim.fn.pathshorten(filename) end -- Handle empty filenames if filename == "" then filename = "[No Name]" end -- Get modified status local modified_bool = vim.bo.modified local modifiable_bool = vim.bo.modifiable local modified = "" if modified_bool then modified = "[+]" end if not modifiable_bool then modified = "[-]" end -- append space if not empty if modified ~= "" then modified = " "..modified end -- Compose component return before..filename..modified..after end M.components.progress = function (args) args = args or {} local before = args.before or "" local after = args.after or "" -- Neovide breaks this component. if vim.g.neovide then return "" end local sbar = { '🭶', '🭷', '🭸', '🭹', '🭺', '🭻' } local curr_line = vim.api.nvim_win_get_cursor(0)[1] or 0 local lines = vim.api.nvim_buf_line_count(0) or 0 if lines == 0 then return "" end -- prevent division by zero local i = math.floor((curr_line - 1) / lines * #sbar) + 1 local prog = string.rep(sbar[i], 2) return before..hi_pattern:format("Visual", prog)..after end M.components.cwd = function (args) args = args or {} local before = args.before or "" local after = args.after or "" local shorten_percentage = args.shorten_percentage or 30 local cwd = vim.fn.getcwd(0) cwd = vim.fn.fnamemodify(cwd, ":~") if utils.component_takes_percentage(#cwd, shorten_percentage) then cwd = vim.fn.pathshorten(cwd) end local trail = cwd:sub(-1) == '/' and '' or "/" return before..cwd..trail..after end M.components.diagnostics = function (args) args = args or {} local before = args.before or "" local after = args.after or "" -- Define icons local error_icon = M.use_icons and " " or "E" local warning_icon = M.use_icons and " " or "W" local info_icon = M.use_icons and " " or "I" local hint_icon = M.use_icons and " " or "H" -- Create empty diagnostics table local diagnostics = {} -- Count diagnostics local errors = #vim.diagnostic.get(0, { severity = 1 }) if errors > 0 then table.insert(diagnostics, hi_pattern:format("DiagnosticSignError", ("%s%s"):format(error_icon, errors))) end local warnings = #vim.diagnostic.get(0, { severity = 2 }) if warnings > 0 then table.insert(diagnostics, hi_pattern:format("DiagnosticSignWarn", ("%s%s"):format(warning_icon, warnings))) end local infos = #vim.diagnostic.get(0, { severity = 3 }) if infos > 0 then table.insert(diagnostics, hi_pattern:format("DiagnosticSignInfo", ("%s%s"):format(info_icon, infos))) end local hints = #vim.diagnostic.get(0, { severity = 4 }) if hints > 0 then table.insert(diagnostics, hi_pattern:format("DiagnosticSignHint", ("%s%s"):format(hint_icon, hints))) end -- Don't show diagnostics in insert mode. if vim.api.nvim_get_mode().mode:find "i" then return "" end local icon = M.use_icons and '' or 'diag: ' local status = hi_pattern:format("Statusline", table.concat(diagnostics, " ")) return before..icon..status..after end M.components.git_branch = function (args) args = args or {} local before = args.before or "" local after = args.after or "" if not vim.b.minigit_summary then return "" end local branch = vim.b.minigit_summary.head_name or "" local icon = M.use_icons and " " or "branch: " return before..icon..branch..after end M.components.git_status = function (args) args = args or {} local before = args.before or "" local after = args.after or "" if not vim.b.minidiff_summary then return "" end local summary = vim.b.minidiff_summary local status = {} local add_icon = M.use_icons and " " or "+" local change_icon = M.use_icons and " " or "~" local delete_icon = M.use_icons and " " or "-" if (summary.add or 0) > 0 then table.insert(status, hi_pattern:format("Added", ("%s%s"):format(add_icon, summary.add))) end if (summary.change or 0) > 0 then table.insert(status, hi_pattern:format("Changed", ("%s%s"):format(change_icon, summary.change))) end if (summary.delete or 0) > 0 then table.insert(status, hi_pattern:format("Removed", ("%s%s"):format(delete_icon, summary.delete))) end return before..table.concat(status, " ")..after end M.components.tab_counter = function (args) args = args or {} local before = args.before or "" local after = args.after or "" local tab_list = vim.api.nvim_list_tabpages() local num_tabs = #tab_list local current_tab_name = vim.api.nvim_get_current_tabpage() local current_tab_index = 1 for i = 1, #tab_list, 1 do if tab_list[i] == current_tab_name then current_tab_index = i end end if num_tabs == 1 then return "" end local icon = M.use_icons and "󰓩 " or "tab: " return before..icon..current_tab_index.."/"..num_tabs..after end M.components.session_name = function (args) args = args or {} local before = args.before or "" local after = args.after or "" if not vim.g.loaded_auto_session then return "" end return before..require("auto-session.lib").current_session_name(true)..after end M.components.markdown_word_count = function (args) args = args or {} local before = args.before or "" local after = args.after or "" if vim.bo.filetype ~= "markdown" then return "" end local word_count = vim.fn.wordcount().visual_words or vim.fn.wordcount().words local icon = M.use_icons and "󱀽 " or "words: " return before..icon..word_count..after end -- Setup the statusline function M.setup(opts) opts = opts or {} M.use_icons = opts.use_icons or false local statusline_components = opts.components or function() return { M.components.mode({before = " "}), M.components.git_branch(), M.components.cwd(), "%=", M.components.session_name(), M.components.markdown_word_count(), M.components.tab_counter(), M.components.location({after = vim.g.neovide and " " or ""}), M.components.progress(), } end local gaps = opts.gaps or " " -- Global function to construct the line Statusline_builder = function () --get a table of all the statusline strings local statusline_strings = statusline_components() -- Remove empty strings to prevent concat issues for i = #statusline_strings, 1, -1 do if statusline_strings[i] == "" then table.remove(statusline_strings, i) end end -- Concatentate strings with gaps return table.concat(statusline_strings, gaps) end -- Tell vim to call the function to construct the line vim.o.statusline = "%{%v:lua.Statusline_builder()%}" end return M