mirror of
https://github.com/neovim/neovim.git
synced 2026-06-24 08:48:16 +00:00
Problem: `nvim_create_autocmd` is too verbose and its `callback` requires extra "nesting". Solution: Introduce `nvim_on`. Start using it internally. Then we can get a feel for how it should look before making it public.
205 lines
6.9 KiB
Lua
205 lines
6.9 KiB
Lua
-- 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.
|
|
-- TODO: move to _core/defaults.lua once it is possible to assign a Lua function to options #25672
|
|
--- @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
|
|
|
|
-- TODO: move to _core/defaults.lua once it is possible to assign a Lua function to options #25672
|
|
function M.space_above()
|
|
add_blank(true)
|
|
end
|
|
|
|
-- TODO: move to _core/defaults.lua once it is possible to assign a Lua function to options #25672
|
|
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://<domain>/<user>/<name>"
|
|
--- @param target string Identifier of a target, like commit hash or tag name
|
|
--- @param target_type "commit"|"tag"
|
|
--- @return string? # Example: <repo>/releases/tag/<target>
|
|
function M.get_forge_url(repo, target, target_type)
|
|
-- The structure <host>/<middle>/<target> 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
|
|
|
|
--- 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
|
|
|
|
--- 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
|