-- This is a modified version of mini.starter -- Check https://github.com/echasnovski/mini.nvim/blob/main/lua/mini/starter.lua local M = {} local H = {} -- {{{ Setup M.setup = function(config) if config and config.header then M.config.header = config.header end local augroup = vim.api.nvim_create_augroup("M", {}) local on_vimenter = function() if vim.fn.argc() > 0 then return end H.is_in_vimenter = true M.open() end vim.api.nvim_create_autocmd("VimEnter", { group = augroup, nested = true, once = true, callback = on_vimenter, desc = "Open on VimEnter", }) local set_default_hl = function(name, data) data.default = true vim.api.nvim_set_hl(0, name, data) end set_default_hl("MiniStarterFooter", { link = "Title" }) set_default_hl("MiniStarterHeader", { link = "Title" }) end -- }}} -- {{{ Open M.open = function(buf_id) -- Ensure proper buffer and open it if H.is_in_vimenter then buf_id = vim.api.nvim_get_current_buf() end if buf_id == nil or not vim.api.nvim_buf_is_valid(buf_id) then buf_id = vim.api.nvim_create_buf(false, true) end vim.api.nvim_set_current_buf(buf_id) -- {{{ Autocmds local augroup = vim.api.nvim_create_augroup("MBuffer", {}) local au = function(event, callback, desc) vim.api.nvim_create_autocmd( event, { group = augroup, buffer = buf_id, callback = callback, desc = desc } ) end au("VimResized", function() M.refresh(buf_id) end, "Refresh") local cache_showtabline = vim.o.showtabline au("BufLeave", function() if vim.o.cmdheight > 0 then vim.cmd("echo ''") end if vim.o.showtabline == 1 then vim.o.showtabline = cache_showtabline end end, "On BufLeave") -- }}} -- {{{ Buffer options -- Force Normal mode. NOTEs: -- - Using `vim.cmd('normal! \28\14')` weirdly does not work. -- - Using `vim.api.nvim_input([[]])` doesn't play nice if `` -- mapping is present (maybe due to non-blocking nature of `nvim_input()`). vim.api.nvim_feedkeys("\28\14", "nx", false) -- Having `noautocmd` is crucial for performance: ~9ms without it, ~1.6ms with it vim.cmd("noautocmd silent! set filetype=starter") local options = { -- Taken from 'vim-startify' "bufhidden=wipe", "colorcolumn=", "foldcolumn=0", "matchpairs=", "nobuflisted", "nocursorcolumn", "nocursorline", "nolist", "nonumber", "noreadonly", "norelativenumber", "nospell", "noswapfile", "signcolumn=no", "synmaxcol&", -- Differ from 'vim-startify' "buftype=nofile", "nomodeline", "nomodifiable", "foldlevel=999", "nowrap", } -- Vim's `setlocal` is currently more robust compared to `opt_local` vim.cmd(("silent! noautocmd setlocal %s"):format(table.concat(options, " "))) -- Hide tabline on single tab by setting `showtabline` to default value (but -- not statusline as it weirdly feels 'naked' without it). vim.o.showtabline = 1 -- }}} M.refresh() H.is_in_vimenter = false end -- }}} -- {{{ Refresh M.refresh = function(buf_id) buf_id = buf_id or vim.api.nvim_get_current_buf() if vim.api.nvim_buf_get_option(buf_id, "ft") ~= "starter" then return end local config = M.config -- Normalize certain config values local header = H.normalize_header_footer(config.header) local footer = H.normalize_header_footer(config.footer) -- {{{ Evaluate content local header_units = {} for _, l in ipairs(header) do table.insert( header_units, { H.content_unit(l, "header", "MiniStarterHeader") } ) end H.content_add_empty_lines(header_units, #header > 0 and 1 or 0) local footer_units = {} for _, l in ipairs(footer) do table.insert( footer_units, { H.content_unit(l, "footer", "MiniStarterFooter") } ) end local content = H.concat_tables( H.gen_hook.aligning("center", nil, header_units, buf_id), H.gen_hook.aligning("center", nil, footer_units, buf_id) ) content = H.gen_hook.aligning(nil, "center", content, buf_id) -- }}} -- Add content vim.api.nvim_buf_set_option(buf_id, "modifiable", true) vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, H.content_to_lines(content)) vim.api.nvim_buf_set_option(buf_id, "modifiable", false) -- {{{ Add highlighting for l_num, content_line in ipairs(content) do -- Track 0-based starting column of current unit (using byte length) local start_col = 0 for _, unit in ipairs(content_line) do if unit.hl ~= nil then -- Use `priority` because of the regression bug (highlights are not stacked -- properly): https://github.com/neovim/neovim/issues/17358 vim.highlight.range( buf_id, H.ns.general, unit.hl, { l_num - 1, start_col }, { l_num - 1, start_col + unit.string:len() }, { priority = 50 } ) end start_col = start_col + unit.string:len() end end -- }}} end M.close = function(buf_id) buf_id = buf_id or vim.api.nvim_get_current_buf() -- Use `pcall` to allow calling for already non-existing buffer pcall(vim.api.nvim_buf_delete, buf_id, {}) end -- }}} -- {{{ Content helpers H.gen_hook = {} H.gen_hook.padding = function(left, top) left = math.max(left or 0, 0) top = math.max(top or 0, 0) return function(content, _) -- Add left padding local left_pad = string.rep(" ", left) for _, line in ipairs(content) do local is_empty_line = #line == 0 or (#line == 1 and line[1].string == "") if not is_empty_line then table.insert(line, 1, H.content_unit(left_pad, "empty", nil)) end end -- Add top padding local top_lines = {} for _ = 1, top do table.insert(top_lines, { H.content_unit("", "empty", nil) }) end content = vim.list_extend(top_lines, content) return content end end H.gen_hook.aligning = function(horizontal, vertical, content, buf_id) horizontal = horizontal or "left" vertical = vertical or "top" local horiz_coef = ({ left = 0, center = 0.5, right = 1.0 })[horizontal] local vert_coef = ({ top = 0, center = 0.5, bottom = 1.0 })[vertical] local win_id = vim.fn.bufwinid(buf_id) if win_id < 0 then return content end local line_strings = H.content_to_lines(content) -- Align horizontally -- Don't use `string.len()` to account for multibyte characters local lines_width = vim.tbl_map(function(l) return vim.fn.strdisplaywidth(l) end, line_strings) local min_right_space = vim.api.nvim_win_get_width(win_id) - math.max(unpack(lines_width)) local left_pad = math.max(math.floor(horiz_coef * min_right_space), 0) -- Align vertically local bottom_space = vim.api.nvim_win_get_height(win_id) - #line_strings local top_pad = math.max(math.floor(vert_coef * bottom_space), 0) return H.gen_hook.padding(left_pad, top_pad)(content) end -- }}} -- {{{ Helpers -- Namespaces for highlighting H.ns = { general = vim.api.nvim_create_namespace(""), } H.normalize_header_footer = function(x) if type(x) == "function" then x = x() end local res = tostring(x) if res == "" then return {} end return vim.split(res, "\n") end H.content_unit = function(string, type, hl, extra) return vim.tbl_extend( "force", { string = string, type = type, hl = hl }, extra or {} ) end H.content_add_empty_lines = function(content, n) for _ = 1, n do table.insert(content, { H.content_unit("", "empty", nil) }) end end --- Convert content to buffer lines --- --- One buffer line is made by concatenating `string` element of units within --- same content line. --- ---@param content table Content "2d array" --- ---@return table Array of strings for each buffer line. H.content_to_lines = function(content) return vim.tbl_map(function(content_line) return table.concat( -- Ensure that each content line is indeed a single buffer line vim.tbl_map(function(x) return x.string:gsub("\n", " ") end, content_line), "" ) end, content) end function H.concat_tables(t1, t2) for i = 1, #t2 do t1[#t1 + 1] = t2[i] end return t1 end -- }}} -- {{{ Lazy footer local version = vim.version() local version_string = "🚀 " .. version.major .. "." .. version.minor .. "." .. version.patch local lazy_stats = nil vim.api.nvim_create_autocmd("User", { pattern = "LazyVimStarted", callback = function() local lazy_ok, lazy = pcall(require, "lazy") if lazy_ok then lazy_stats = { total_plugins = lazy.stats().count .. " Plugins", startup_time = math.floor(lazy.stats().startuptime * 100 + 0.5) / 100, } require("my.starter").refresh() end end, }) function H.lazy_stats_item() if lazy_stats ~= nil then return version_string .. " — 🧰 " .. lazy_stats.total_plugins .. " — 🕐 " .. lazy_stats.startup_time .. "ms" else return version_string end end -- }}} M.config = { header = "Hello world!", footer = H.lazy_stats_item, } return M