151 lines
5.1 KiB
Lua
151 lines
5.1 KiB
Lua
---@class lazyvim.util.mini
|
|
local M = {}
|
|
|
|
---@alias Mini.ai.loc {line:number, col:number}
|
|
---@alias Mini.ai.region {from:Mini.ai.loc, to:Mini.ai.loc}
|
|
|
|
-- Mini.ai indent text object
|
|
-- For "a", it will include the non-whitespace line surrounding the indent block.
|
|
-- "a" is line-wise, "i" is character-wise.
|
|
function M.ai_indent(ai_type)
|
|
local spaces = (" "):rep(vim.o.tabstop)
|
|
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
|
local indents = {} ---@type {line: number, indent: number, text: string}[]
|
|
|
|
for l, line in ipairs(lines) do
|
|
if not line:find("^%s*$") then
|
|
indents[#indents + 1] = { line = l, indent = #line:gsub("\t", spaces):match("^%s*"), text = line }
|
|
end
|
|
end
|
|
|
|
local ret = {} ---@type (Mini.ai.region | {indent: number})[]
|
|
|
|
for i = 1, #indents do
|
|
if i == 1 or indents[i - 1].indent < indents[i].indent then
|
|
local from, to = i, i
|
|
for j = i + 1, #indents do
|
|
if indents[j].indent < indents[i].indent then
|
|
break
|
|
end
|
|
to = j
|
|
end
|
|
from = ai_type == "a" and from > 1 and from - 1 or from
|
|
to = ai_type == "a" and to < #indents and to + 1 or to
|
|
ret[#ret + 1] = {
|
|
indent = indents[i].indent,
|
|
from = { line = indents[from].line, col = ai_type == "a" and 1 or indents[from].indent + 1 },
|
|
to = { line = indents[to].line, col = #indents[to].text },
|
|
}
|
|
end
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
-- taken from MiniExtra.gen_ai_spec.buffer
|
|
function M.ai_buffer(ai_type)
|
|
local start_line, end_line = 1, vim.fn.line("$")
|
|
if ai_type == "i" then
|
|
-- Skip first and last blank lines for `i` textobject
|
|
local first_nonblank, last_nonblank = vim.fn.nextnonblank(start_line), vim.fn.prevnonblank(end_line)
|
|
-- Do nothing for buffer with all blanks
|
|
if first_nonblank == 0 or last_nonblank == 0 then
|
|
return { from = { line = start_line, col = 1 } }
|
|
end
|
|
start_line, end_line = first_nonblank, last_nonblank
|
|
end
|
|
|
|
local to_col = math.max(vim.fn.getline(end_line):len(), 1)
|
|
return { from = { line = start_line, col = 1 }, to = { line = end_line, col = to_col } }
|
|
end
|
|
|
|
-- register all text objects with which-key
|
|
function M.ai_whichkey()
|
|
local objects = {
|
|
{ " ", desc = "whitespace" },
|
|
{ '"', desc = 'balanced "' },
|
|
{ "'", desc = "balanced '" },
|
|
{ "(", desc = "balanced (" },
|
|
{ ")", desc = "balanced ) including white-space" },
|
|
{ "<", desc = "balanced <" },
|
|
{ ">", desc = "balanced > including white-space" },
|
|
{ "?", desc = "user prompt" },
|
|
{ "U", desc = "use/call without dot in name" },
|
|
{ "[", desc = "balanced [" },
|
|
{ "]", desc = "balanced ] including white-space" },
|
|
{ "_", desc = "underscore" },
|
|
{ "`", desc = "balanced `" },
|
|
{ "a", desc = "argument" },
|
|
{ "b", desc = "balanced )]}" },
|
|
{ "c", desc = "class" },
|
|
{ "d", desc = "digit(s)" },
|
|
{ "e", desc = "word in CamelCase & snake_case" },
|
|
{ "f", desc = "function" },
|
|
{ "g", desc = "entire file" },
|
|
{ "i", desc = "indent" },
|
|
{ "o", desc = "block, conditional, loop" },
|
|
{ "q", desc = "quote `\"'" },
|
|
{ "t", desc = "tag" },
|
|
{ "u", desc = "use/call function & method" },
|
|
{ "{", desc = "balanced {" },
|
|
{ "}", desc = "balanced } including white-space" },
|
|
}
|
|
|
|
local ret = { mode = { "o", "x" } }
|
|
for prefix, name in pairs({
|
|
i = "inside",
|
|
a = "around",
|
|
il = "last",
|
|
["in"] = "next",
|
|
al = "last",
|
|
an = "next",
|
|
}) do
|
|
ret[#ret + 1] = { prefix, group = name }
|
|
for _, obj in ipairs(objects) do
|
|
ret[#ret + 1] = { prefix .. obj[1], desc = obj.desc }
|
|
end
|
|
end
|
|
require("which-key").add(ret, { notify = false })
|
|
end
|
|
|
|
---@param opts {skip_next: string, skip_ts: string[], skip_unbalanced: boolean, markdown: boolean}
|
|
function M.pairs(opts)
|
|
local pairs = require("mini.pairs")
|
|
pairs.setup(opts)
|
|
local open = pairs.open
|
|
pairs.open = function(pair, neigh_pattern)
|
|
if vim.fn.getcmdline() ~= "" then
|
|
return open(pair, neigh_pattern)
|
|
end
|
|
local o, c = pair:sub(1, 1), pair:sub(2, 2)
|
|
local line = vim.api.nvim_get_current_line()
|
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
|
local next = line:sub(cursor[2] + 1, cursor[2] + 1)
|
|
local before = line:sub(1, cursor[2])
|
|
if opts.markdown and o == "`" and vim.bo.filetype == "markdown" and before:match("^%s*``") then
|
|
return "`\n```" .. vim.api.nvim_replace_termcodes("<up>", true, true, true)
|
|
end
|
|
if opts.skip_next and next ~= "" and next:match(opts.skip_next) then
|
|
return o
|
|
end
|
|
if opts.skip_ts and #opts.skip_ts > 0 then
|
|
local ok, captures = pcall(vim.treesitter.get_captures_at_pos, 0, cursor[1] - 1, math.max(cursor[2] - 1, 0))
|
|
for _, capture in ipairs(ok and captures or {}) do
|
|
if vim.tbl_contains(opts.skip_ts, capture.capture) then
|
|
return o
|
|
end
|
|
end
|
|
end
|
|
if opts.skip_unbalanced and next == c and c ~= o then
|
|
local _, count_open = line:gsub(vim.pesc(pair:sub(1, 1)), "")
|
|
local _, count_close = line:gsub(vim.pesc(pair:sub(2, 2)), "")
|
|
if count_close > count_open then
|
|
return o
|
|
end
|
|
end
|
|
return open(pair, neigh_pattern)
|
|
end
|
|
end
|
|
|
|
return M
|