---@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 ---@param opts table function M.ai_whichkey(opts) local objects = { { " ", desc = "whitespace" }, { '"', desc = '" string' }, { "'", desc = "' string" }, { "(", desc = "() block" }, { ")", desc = "() block with ws" }, { "<", desc = "<> block" }, { ">", desc = "<> block with ws" }, { "?", desc = "user prompt" }, { "U", desc = "use/call without dot" }, { "[", desc = "[] block" }, { "]", desc = "[] block with ws" }, { "_", desc = "underscore" }, { "`", desc = "` string" }, { "a", desc = "argument" }, { "b", desc = ")]} block" }, { "c", desc = "class" }, { "d", desc = "digit(s)" }, { "e", desc = "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" }, { "{", desc = "{} block" }, { "}", desc = "{} with ws" }, } local ret = { mode = { "o", "x" } } ---@type table local mappings = vim.tbl_extend("force", {}, { around = "a", inside = "i", around_next = "an", inside_next = "in", around_last = "al", inside_last = "il", }, opts.mappings or {}) mappings.goto_left = nil mappings.goto_right = nil for name, prefix in pairs(mappings) do name = name:gsub("^around_", ""):gsub("^inside_", "") ret[#ret + 1] = { prefix, group = name } for _, obj in ipairs(objects) do local desc = obj.desc if prefix:sub(1, 1) == "i" then desc = desc:gsub(" with ws", "") end 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) LazyVim.toggle.map("up", { name = "Mini Pairs", get = function() return not vim.g.minipairs_disable end, set = function(state) vim.g.minipairs_disable = not state end, }) 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("", 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