diff --git a/lua/mini/starter.lua b/lua/mini/starter.lua index 66d46a52..9b7e4397 100644 --- a/lua/mini/starter.lua +++ b/lua/mini/starter.lua @@ -138,7 +138,7 @@ --- local timer = vim.loop.new_timer() --- local n_seconds = 0 --- timer:start(0, 1000, vim.schedule_wrap(function() ---- if vim.bo.filetype ~= 'ministarter' then +--- if vim.bo.filetype ~= 'dashboard' then --- timer:stop() --- return --- end @@ -239,6 +239,9 @@ MiniStarter.config = { -- If `nil` (default), default items will be used (see |mini.starter|). items = nil, + -- Empty lines at the top of the content. + top_padding = 0, + -- Header to be displayed before items. Converted to single string via -- `tostring` (use `\n` to display several lines). If function, it is -- evaluated first. If `nil` (default), polite greeting will be used. @@ -249,6 +252,12 @@ MiniStarter.config = { -- evaluated first. If `nil` (default), default usage help will be shown. footer = nil, + -- Amount of empty lines between items and the footer. + footer_offset = 1, + + -- Picture to show to the side of the content. + picture = nil, + -- Array of functions to be applied consecutively to initial content. -- Each function should take and return content for Starter buffer (see -- |mini.starter| and |MiniStarter.get_content()| for more details). @@ -373,7 +382,7 @@ MiniStarter.refresh = function(buf_id) data.footer = H.normalize_header_footer(config.footer or H.default_footer) -- Evaluate content - local content = H.make_initial_content(data.header, items, data.footer) + local content = H.make_initial_content(data.header, items, data.footer, config) local hooks = config.content_hooks or H.default_content_hooks for _, f in ipairs(hooks) do content = f(content, buf_id) @@ -400,6 +409,16 @@ MiniStarter.refresh = function(buf_id) -- Apply current query (clear command line afterwards) H.make_query(buf_id) + + -- The picture drawn here used to get cleared at some point + -- that _usually_ came before any scheduled timers fired. + -- That seems to not happen anymore (nvim-0.11.1, kitty 0.42.1). + H.draw_picture(config, false) + --local timer = vim.loop.new_timer() + --timer:start(0, 0, vim.schedule_wrap(function() + -- timer:stop() + -- H.draw_picture(config, false) + --end)) end --- Close Starter buffer @@ -729,18 +748,45 @@ MiniStarter.gen_hook.aligning = function(horizontal, vertical) local win_id = vim.fn.bufwinid(buf_id) if win_id < 0 then return end + local config = H.get_config() + local line_strings = MiniStarter.content_to_lines(content) + H.win_width = vim.api.nvim_win_get_width(win_id) + H.win_height = vim.api.nvim_win_get_height(win_id) + -- 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)) + -- Ignore the max line width + local min_right_space = H.win_width -- - 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 bottom_space = H.win_height - #line_strings local top_pad = math.max(math.floor(vert_coef * bottom_space), 0) + -- Position picture + if config.picture ~= nil then + if config.picture.width == nil then config.picture.width = 25 end + if config.picture.position ~= 'right' then config.picture.position = 'left' end + if config.picture.padding == nil then config.picture.padding = {} end + if config.picture.padding.left == nil then config.picture.padding.left = 0 end + if config.picture.padding.top == nil then config.picture.padding.top = 0 end + if config.picture.padding.text == nil then config.picture.padding.text = 0 end + + local picture_left_pad = (config.picture.width / 2.0) - config.picture.padding.left + + left_pad = left_pad + config.picture.padding.text + if config.picture.position == 'left' then + H.picture_x = left_pad - picture_left_pad + else + H.picture_x = left_pad + picture_left_pad + end + if H.picture_x < 0.0 then H.picture_x = 0.0 end + H.picture_y = top_pad + config.picture.padding.top + config.top_padding + end + return MiniStarter.gen_hook.padding(left_pad, top_pad)(content) end end @@ -1003,7 +1049,7 @@ H.default_footer = [[ Type query to filter items deletes latest character from query resets current query -, , move current item + move current item executes action of current item closes this buffer]] @@ -1044,8 +1090,54 @@ end H.apply_config = function(config) MiniStarter.config = config end +H.is_file_readable = function(path) + return vim.fn.filereadable(vim.fn.expand(path)) ~= 0 +end + +H.draw_picture = function(config, clear) + if config ~= nil then + if config.picture == nil or config.picture.path == nil or not H.is_file_readable(config.picture.path) then return end + else + -- No picture, so nothing to clear + if H.picture_x == nil or H.picture_y == nil then return end + end + local cmd = '' + if clear and not config.picture.kitty then + cmd = ('for i in $(seq %d %d); do tput cup $i 0 && tput el; done'):format(0, H.win_height - 1) + else + cmd = ('chafa %s --align left,top -s %dx%d 2>&1'):format(config.picture.path, config.picture.width, (H.win_height - 2) - H.picture_y) + end + -- Resizing will not clear on non-kitty mode + local output = '' + if config.picture.kitty then + output = output .. '\027_Ga=d\027\\' + end + if not clear or not config.picture.kitty then + local handle = io.popen(cmd) + output = output .. handle:read('*a') + -- `rc` doesn't work when run from inside vim (last tested in neovim-0.9) + local rc = {handle:close()} + end + local tty, err = io.open('/proc/self/fd/0', 'wb') + if tty then + tty:write('\x1b[s') + if not clear then + tty:write(('\x1b[%d;%dH'):format(H.picture_y, H.picture_x)) + elseif not config.picture.kitty then + tty:write('\x1b[48;5;0m') + end + tty:write(output) + if not clear then + tty:write('\x1b[u') + end + tty:close() + else + H.echo('Error opening tty: ' .. err) + end +end + H.create_autocommands = function(config) - local gr = vim.api.nvim_create_augroup('MiniStarter', {}) + H.augroup = vim.api.nvim_create_augroup('MiniStarter', {}) if config.autoopen then local on_vimenter = function() @@ -1057,7 +1149,7 @@ H.create_autocommands = function(config) vim.cmd('noautocmd lua MiniStarter.open()') end - local au_opts = { group = gr, nested = true, once = true, callback = on_vimenter, desc = 'Open on VimEnter' } + local au_opts = { group = H.augroup, nested = true, once = true, callback = on_vimenter, desc = 'Open on VimEnter' } vim.api.nvim_create_autocmd('VimEnter', au_opts) end @@ -1103,9 +1195,11 @@ H.normalize_header_footer = function(x) end -- Work with buffer content --------------------------------------------------- -H.make_initial_content = function(header, items, footer) +H.make_initial_content = function(header, items, footer, config) local content = {} + H.content_add_empty_lines(content, config.top_padding) + -- Add header lines for _, l in ipairs(header) do H.content_add_line(content, { H.content_unit(l, 'header', 'MiniStarterHeader') }) @@ -1116,7 +1210,7 @@ H.make_initial_content = function(header, items, footer) H.content_add_items(content, items) -- Add footer lines - H.content_add_empty_lines(content, #footer > 0 and 1 or 0) + H.content_add_empty_lines(content, #footer > 0 and config.footer_offset or 0) for _, l in ipairs(footer) do H.content_add_line(content, { H.content_unit(l, 'footer', 'MiniStarterFooter') }) end @@ -1319,11 +1413,23 @@ H.make_buffer_autocmd = function(buf_id) au('VimResized', function() MiniStarter.refresh(buf_id) end, 'Refresh') au('CursorMoved', function() H.position_cursor_on_current_item(buf_id) end, 'Position cursor') + local config = H.get_config() + 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 + H.draw_picture(config, true) end, 'On BufLeave') + + au('BufEnter', function() + H.draw_picture(config, false) + --local timer = vim.loop.new_timer() + --timer:start(0, 0, vim.schedule_wrap(function() + -- timer:stop() + -- H.draw_picture(config, false) + --end)) + end, 'On BufEnter') end H.apply_buffer_options = function(buf_id) @@ -1336,7 +1442,7 @@ H.apply_buffer_options = function(buf_id) 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=ministarter') + vim.cmd('noautocmd silent! set filetype=dashboard') local options = { -- Taken from 'vim-startify' @@ -1383,12 +1489,8 @@ H.apply_buffer_mappings = function(buf_id) buf_keymap('', 'MiniStarter.eval_current_item()') - buf_keymap('', [[MiniStarter.update_current_item('prev')]]) - buf_keymap('', [[MiniStarter.update_current_item('prev')]]) - buf_keymap('', [[MiniStarter.update_current_item('prev')]]) - buf_keymap('', [[MiniStarter.update_current_item('next')]]) - buf_keymap('', [[MiniStarter.update_current_item('next')]]) - buf_keymap('', [[MiniStarter.update_current_item('next')]]) + buf_keymap('', [[MiniStarter.update_current_item('prev')]]) + buf_keymap('', [[MiniStarter.update_current_item('next')]]) -- Make all special symbols to update query for _, key in ipairs(vim.split(H.get_config().query_updaters, '')) do @@ -1464,21 +1566,21 @@ H.is_something_shown = function() end -- Utilities ------------------------------------------------------------------ -H.error = function(msg) error('(mini.starter) ' .. msg, 0) end +H.error = function(msg) error('' .. msg, 0) end H.check_type = function(name, val, ref, allow_nil) if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end H.error(string.format('`%s` should be %s, not %s', name, ref, type(val))) end -H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'ministarter://' .. buf_id .. '/' .. name) end +H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, '*dashboard*') end H.echo = function(msg, is_important) if H.get_config().silent then return end -- Construct message chunks msg = type(msg) == 'string' and { { msg } } or msg - table.insert(msg, 1, { '(mini.starter) ', 'WarningMsg' }) + table.insert(msg, 1, { '', 'Warning' }) -- Avoid hit-enter-prompt local max_width = vim.o.columns * math.max(vim.o.cmdheight - 1, 0) + vim.v.echospace @@ -1527,7 +1629,7 @@ H.eval_fun_or_string = function(x, string_as_cmd) if type(x) == 'function' then return x() end if type(x) == 'string' then if string_as_cmd then - vim.cmd(x) + pcall(function() vim.cmd(x) end) else return x end