-- Nursery for random things that may later find their way into stdlib if they mature. local M = {} --- Adds one or more blank lines above or below the cursor. --- @param above? boolean Place blank line(s) above the cursor local function add_blank(above) local offset = above and 1 or 0 local repeated = vim.fn['repeat']({ '' }, vim.v.count1) local linenr = vim.api.nvim_win_get_cursor(0)[1] vim.api.nvim_buf_set_lines(0, linenr - offset, linenr - offset, true, repeated) end function M.space_above() add_blank(true) end function M.space_below() add_blank() end --- Gets a buffer by name --- @param name string --- @return integer? function M.get_buf_by_name(name) for _, buf in ipairs(vim.api.nvim_list_bufs()) do if vim.api.nvim_buf_get_name(buf) == name then return buf end end end --- Edit a file in a specific window --- @param winnr number --- @param file string --- @return number buffer number of the edited buffer M.edit_in = function(winnr, file) local function resolved_path(path) if not path or path == '' then return '' end return vim.fn.resolve(vim.fs.abspath(path)) end return vim.api.nvim_win_call(winnr, function() local current_buf = vim.api.nvim_win_get_buf(winnr) local current = resolved_path(vim.api.nvim_buf_get_name(current_buf)) -- Check if the current buffer is already the target file if current == resolved_path(file) then return current_buf end -- Read the file into the buffer vim.cmd.edit(vim.fn.fnameescape(file)) return vim.api.nvim_get_current_buf() end) end --- :edit, but it respects commands like :hor, :vert, :tab, etc. --- @param file string --- @param mods string|vim.api.keyset.cmd_mods Modifier string ("vertical") or structured mods table. function M.wrapped_edit(file, mods) assert(mods) if type(mods) == 'string' then mods = vim.api.nvim_parse_cmd(mods .. ' edit').mods --[[@as vim.api.keyset.cmd_mods]] end --- @cast mods vim.api.keyset.cmd_mods if (mods.tab or 0) > 0 or (mods.split or '') ~= '' or mods.horizontal or mods.vertical then local buf = M.get_buf_by_name(file) if buf == nil then buf = vim.api.nvim_create_buf(true, false) end vim.cmd.sbuffer { buf, mods = mods } end vim.cmd.edit { file } end --- Read a chunk of data from a file --- @param file string --- @param size number --- @return string? chunk or nil on error function M.read_chunk(file, size) local fd = io.open(file, 'rb') if not fd then return nil end local chunk = fd:read(size) fd:close() return tostring(chunk) end --- Check if a range in a buffer is inside a Lua codeblock via treesitter injection. --- Used by :source to detect Lua code in non-Lua files (e.g., vimdoc). --- @param bufnr integer Buffer number --- @param line1 integer Start line (1-indexed) --- @param line2 integer End line (1-indexed) --- @return boolean True if the range is in a Lua injection function M.source_is_lua(bufnr, line1, line2) local parser = vim.treesitter.get_parser(bufnr) if not parser then return false end -- Parse from buffer start through one line past line2 to include injection closing markers local range = { line1 - 1, 0, line2 - 1, -1 } parser:parse({ 0, 0, line2, -1 }) local lang_tree = parser:language_for_range(range) return lang_tree:lang() == 'lua' end --- Returns the exit code string for the current buffer, given: --- - Channel is attached to the current buffer --- - Current buffer is a terminal buffer --- --- @return string function M.term_exitcode() local chan_id = vim.bo.channel if chan_id == 0 or vim.bo.buftype ~= 'terminal' then return '' end local info = vim.api.nvim_get_chan_info(chan_id) if info.exitcode and info.exitcode >= 0 then return string.format('[Exit: %d]', info.exitcode) end return '' end --- Compute a link to a target on a forge host --- @param repo string URL of repo, usually "https:////" --- @param target string Identifier of a target, like commit hash or tag name --- @param target_type "commit"|"tag" --- @return string? # Example: /releases/tag/ function M.get_forge_url(repo, target, target_type) -- The structure // works for most forges. Like: -- - https://github.com/neovim/nvim-lspconfig/commit/e146efa -- - https://github.com/neovim/nvim-lspconfig/releases/tag/v2.8.0 local ref_middles = { { pattern = '^https://github%.com/', commit = 'commit', tag = 'releases/tag' }, { pattern = '^https://gitlab%.com/', commit = '-/commit', tag = '-/tags' }, { pattern = '^https://git%.sr%.ht/', commit = 'commit', tag = 'refs' }, { pattern = '^https://tangled%.org/', commit = 'commit', tag = 'tags' }, { pattern = '^https://bitbucket%.org/', commit = 'commits', tag = 'src' }, -- Fall back to Forgejo style since there is no fixed host { pattern = '^https://', commit = 'commit', tag = 'src/tag' }, } local middle = '' for _, mid in ipairs(ref_middles) do if repo:match(mid.pattern) then middle = mid[target_type] if middle ~= '' then break end end end if middle == '' then return nil end repo = repo:gsub('/+$', '') return ('%s/%s/%s'):format(repo, middle, target) end --- Gets a scrubbed message from a pcall'd command error (drops Lua context/traceback): --- "…/editor.lua:123: Vim(put):E484: xx" => "E484: xx" --- --- @param err string --- @return string function M.cmd_errmsg(err) err = err:match('^[^\n]*') or err --- @type string err = err:match('Vim%b():%s*(.*)') or err:match('Vim:%s*(.*)') or (err:gsub('^.-:%d+:%s*', '')) return (err:gsub('^Lua:%s*', '')) end --- Utility function for displaying vim error codes (EXX) --- @param msg string function M.echo_err(msg) vim.api.nvim_echo({ { msg } }, true, { err = true }) end --- Shows a message from a builtin plugin, prefixed with the plugin name. --- --- Scheduled, so it is safe to call from |api-fast| contexts. --- @param name string Plugin name, e.g. "zip". --- @param msg string --- @param level? integer Level from |vim.log.levels|. Defaults to ERROR. function M.notify(name, msg, level) vim.schedule(function() vim.notify(('%s: %s'):format(name, msg), level or vim.log.levels.ERROR) end) end --- Define event-handlers (autocmds) ergonomically. --- --- Examples: --- ```lua --- local nvim_on = require('vim._core.util').nvim_on --- nvim_on('BufWritePost', group, function(ev) print(ev.file) end) --- nvim_on({ 'BufRead', 'BufNew' }, group, { pattern = '*.lua' }, function(ev) end) --- nvim_on('VimLeavePre', nil, function() end) --- ``` --- --- @param events vim.api.keyset.events|vim.api.keyset.events[] Event(s) to watch. See |autocmd-events|. --- @param group string|integer? Group name or id, or `nil`. --- @param opts_or_fn vim.api.keyset.create_autocmd Options. --- @param fn fun(ev: vim.api.keyset.create_autocmd.callback_args): boolean? Event handler. --- @return integer # Autocmd id (see |nvim_create_autocmd()|). --- @overload fun(events: vim.api.keyset.events|vim.api.keyset.events[], group: string|integer?, fn: fun(ev: vim.api.keyset.create_autocmd.callback_args): boolean?): integer function M.nvim_on(events, group, opts_or_fn, fn) vim.validate('opts_or_fn', opts_or_fn, { 'function', 'table' }) local opts --- @type vim.api.keyset.create_autocmd if type(opts_or_fn) == 'function' then fn, opts = opts_or_fn, {} else opts = opts_or_fn end opts.group = group opts.callback = fn return vim.api.nvim_create_autocmd(events, opts) end return M