watch text-changes with signals
All checks were successful
NeoVim tests / code-quality (push) Successful in 1m22s
All checks were successful
NeoVim tests / code-quality (push) Successful in 1m22s
This commit is contained in:
parent
7f85848620
commit
ad2e579d1d
3
Makefile
3
Makefile
@ -19,3 +19,6 @@ test:
|
|||||||
@echo "## Generating coverage report"
|
@echo "## Generating coverage report"
|
||||||
@luacov
|
@luacov
|
||||||
@awk '/^Summary$$/{flag=1;next} flag{print}' luacov.report.out
|
@awk '/^Summary$$/{flag=1;next} flag{print}' luacov.report.out
|
||||||
|
|
||||||
|
watch:
|
||||||
|
@watchexec -c -e lua make
|
||||||
|
@ -5,7 +5,7 @@ local Renderer = require('u.renderer').Renderer
|
|||||||
--- @field bufnr number
|
--- @field bufnr number
|
||||||
--- @field b vim.var_accessor
|
--- @field b vim.var_accessor
|
||||||
--- @field bo vim.bo
|
--- @field bo vim.bo
|
||||||
--- @field private renderer u.Renderer
|
--- @field renderer u.renderer.Renderer
|
||||||
local Buffer = {}
|
local Buffer = {}
|
||||||
Buffer.__index = Buffer
|
Buffer.__index = Buffer
|
||||||
|
|
||||||
|
@ -91,14 +91,12 @@ function Range.from_marks(lpos, rpos)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- @param bufnr number
|
--- @param bufnr number
|
||||||
--- @param id number
|
--- @param extmark vim.api.keyset.get_extmark_item_by_id
|
||||||
function Range.from_extmark(bufnr, id)
|
function Range.from_extmark(bufnr, extmark)
|
||||||
local mode = 'v'
|
|
||||||
|
|
||||||
---@type integer, integer, vim.api.keyset.extmark_details | nil
|
---@type integer, integer, vim.api.keyset.extmark_details | nil
|
||||||
local start_row0, start_col0, details =
|
local start_row0, start_col0, details = unpack(extmark)
|
||||||
unpack(vim.api.nvim_buf_get_extmark_by_id(bufnr, NS, id, { details = true }))
|
|
||||||
|
|
||||||
|
local mode = 'v'
|
||||||
local start = Pos.new(bufnr, start_row0 + 1, start_col0 + 1)
|
local start = Pos.new(bufnr, start_row0 + 1, start_col0 + 1)
|
||||||
local stop = details and Pos.new(bufnr, details.end_row + 1, details.end_col)
|
local stop = details and Pos.new(bufnr, details.end_row + 1, details.end_col)
|
||||||
|
|
||||||
@ -651,7 +649,14 @@ end
|
|||||||
|
|
||||||
function ExtmarkRange.new(bufnr, id) return setmetatable({ bufnr = bufnr, id = id }, ExtmarkRange) end
|
function ExtmarkRange.new(bufnr, id) return setmetatable({ bufnr = bufnr, id = id }, ExtmarkRange) end
|
||||||
|
|
||||||
function ExtmarkRange:range() return Range.from_extmark(self.bufnr, self.id) end
|
function ExtmarkRange:range()
|
||||||
|
return Range.from_extmark(
|
||||||
|
self.bufnr,
|
||||||
|
vim.api.nvim_buf_get_extmark_by_id(self.bufnr, NS, self.id, {
|
||||||
|
details = true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
function ExtmarkRange:delete() vim.api.nvim_buf_del_extmark(self.bufnr, NS, self.id) end
|
function ExtmarkRange:delete() vim.api.nvim_buf_del_extmark(self.bufnr, NS, self.id) end
|
||||||
|
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
|
local Signal = require('u.tracker').Signal
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
local H = {}
|
local H = {}
|
||||||
|
|
||||||
--- @alias u.renderer.Tag { kind: 'tag'; name: string, attributes: table<string, unknown>, children: u.renderer.Tree }
|
--- @alias u.renderer.TagEventHandler fun(tag: u.renderer.Tag, mode: string, lhs: string): string
|
||||||
|
|
||||||
|
--- @alias u.renderer.TagAttributes { [string]?: unknown; imap?: table<string, u.renderer.TagEventHandler>; nmap?: table<string, u.renderer.TagEventHandler>; vmap?: table<string, u.renderer.TagEventHandler>; xmap?: table<string, u.renderer.TagEventHandler>; omap?: table<string, u.renderer.TagEventHandler> }
|
||||||
|
|
||||||
|
--- @class u.renderer.Tag
|
||||||
|
--- @field kind 'tag'
|
||||||
|
--- @field name string
|
||||||
|
--- @field attributes u.renderer.TagAttributes
|
||||||
|
--- @field children u.renderer.Tree
|
||||||
|
|
||||||
--- @alias u.renderer.Node nil | boolean | string | u.renderer.Tag
|
--- @alias u.renderer.Node nil | boolean | string | u.renderer.Tag
|
||||||
--- @alias u.renderer.Tree u.renderer.Node | u.renderer.Node[]
|
--- @alias u.renderer.Tree u.renderer.Node | u.renderer.Node[]
|
||||||
|
|
||||||
-- luacheck: ignore
|
-- luacheck: ignore
|
||||||
--- @type table<string, fun(attributes: table<string, any>, children: u.renderer.Tree): u.renderer.Tag> & fun(name: string, attributes: table<string, any>, children: u.renderer.Tree): u.renderer.Tag>
|
--- @type table<string, fun(attributes: u.renderer.TagAttributes, children: u.renderer.Tree): u.renderer.Tag> & fun(name: string, attributes: u.renderer.TagAttributes, children: u.renderer.Tree): u.renderer.Tag>
|
||||||
M.h = setmetatable({}, {
|
M.h = setmetatable({}, {
|
||||||
__call = function(_, name, attributes, children)
|
__call = function(_, name, attributes, children)
|
||||||
return {
|
return {
|
||||||
@ -25,14 +36,19 @@ M.h = setmetatable({}, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
-- Renderer {{{
|
-- Renderer {{{
|
||||||
--- @alias RendererExtmark { id?: number; start: [number, number]; stop: [number, number]; opts: any; tag: any }
|
--- @class u.renderer.RendererExtmark
|
||||||
|
--- @field id? number
|
||||||
|
--- @field start [number, number]
|
||||||
|
--- @field stop [number, number]
|
||||||
|
--- @field opts vim.api.keyset.set_extmark
|
||||||
|
--- @field tag u.renderer.Tag
|
||||||
|
|
||||||
--- @class u.Renderer
|
--- @class u.renderer.Renderer
|
||||||
--- @field bufnr number
|
--- @field bufnr number
|
||||||
--- @field ns number
|
--- @field ns number
|
||||||
--- @field changedtick number
|
--- @field changedtick number
|
||||||
--- @field old { lines: string[]; extmarks: RendererExtmark[] }
|
--- @field old { lines: string[]; extmarks: u.renderer.RendererExtmark[] }
|
||||||
--- @field curr { lines: string[]; extmarks: RendererExtmark[] }
|
--- @field curr { lines: string[]; extmarks: u.renderer.RendererExtmark[] }
|
||||||
local Renderer = {}
|
local Renderer = {}
|
||||||
Renderer.__index = Renderer
|
Renderer.__index = Renderer
|
||||||
M.Renderer = Renderer
|
M.Renderer = Renderer
|
||||||
@ -64,6 +80,12 @@ function Renderer.new(bufnr) -- {{{
|
|||||||
old = { lines = {}, extmarks = {} },
|
old = { lines = {}, extmarks = {} },
|
||||||
curr = { lines = {}, extmarks = {} },
|
curr = { lines = {}, extmarks = {} },
|
||||||
}, Renderer)
|
}, Renderer)
|
||||||
|
|
||||||
|
vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI', 'TextChangedP' }, {
|
||||||
|
buffer = bufnr,
|
||||||
|
callback = function() self:_on_text_changed() end,
|
||||||
|
})
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end -- }}}
|
end -- }}}
|
||||||
|
|
||||||
@ -199,7 +221,7 @@ function Renderer:render(tree) -- {{{
|
|||||||
self.changedtick = changedtick
|
self.changedtick = changedtick
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @type RendererExtmark[]
|
--- @type u.renderer.RendererExtmark[]
|
||||||
local extmarks = {}
|
local extmarks = {}
|
||||||
|
|
||||||
--- @type string[]
|
--- @type string[]
|
||||||
@ -214,31 +236,28 @@ function Renderer:render(tree) -- {{{
|
|||||||
tag.attributes.extmark.hl_group = tag.attributes.extmark.hl_group or hl
|
tag.attributes.extmark.hl_group = tag.attributes.extmark.hl_group or hl
|
||||||
end
|
end
|
||||||
|
|
||||||
local extmark = tag.attributes.extmark
|
local extmark_opts = tag.attributes.extmark or {}
|
||||||
|
|
||||||
-- Set any necessary keymaps:
|
-- Set any necessary keymaps:
|
||||||
for _, mode in ipairs { 'i', 'n', 'v', 'x', 'o' } do
|
for _, mode in ipairs { 'i', 'n', 'v', 'x', 'o' } do
|
||||||
for lhs, _ in pairs(tag.attributes[mode .. 'map'] or {}) do
|
for lhs, _ in pairs(tag.attributes[mode .. 'map'] or {}) do
|
||||||
-- Force creating an extmark if there are key handlers. To accurately
|
-- Force creating an extmark if there are key handlers. To accurately
|
||||||
-- sense the bounds of the text, we need an extmark:
|
-- sense the bounds of the text, we need an extmark:
|
||||||
extmark = extmark or {}
|
|
||||||
vim.keymap.set(
|
vim.keymap.set(
|
||||||
'n',
|
mode,
|
||||||
lhs,
|
lhs,
|
||||||
function() return self:_expr_map_callback('n', lhs) end,
|
function() return self:_expr_map_callback(mode, lhs) end,
|
||||||
{ buffer = self.bufnr, expr = true, replace_keycodes = true }
|
{ buffer = self.bufnr, expr = true, replace_keycodes = true }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if extmark then
|
table.insert(extmarks, {
|
||||||
table.insert(extmarks, {
|
start = start0,
|
||||||
start = start0,
|
stop = stop0,
|
||||||
stop = stop0,
|
opts = extmark_opts,
|
||||||
opts = extmark,
|
tag = tag,
|
||||||
tag = tag,
|
})
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end, -- }}}
|
end, -- }}}
|
||||||
}
|
}
|
||||||
@ -316,9 +335,10 @@ function Renderer:_expr_map_callback(mode, lhs) -- {{{
|
|||||||
local tag = pos_info.tag
|
local tag = pos_info.tag
|
||||||
|
|
||||||
-- is the tag listening?
|
-- is the tag listening?
|
||||||
|
--- @type u.renderer.TagEventHandler?
|
||||||
local f = vim.tbl_get(tag.attributes, mode .. 'map', lhs)
|
local f = vim.tbl_get(tag.attributes, mode .. 'map', lhs)
|
||||||
if type(f) == 'function' then
|
if type(f) == 'function' then
|
||||||
local result = f()
|
local result = f(tag, mode, lhs)
|
||||||
if result == '' then
|
if result == '' then
|
||||||
-- bubble-up to the next tag, but set cancel to true, in case there are
|
-- bubble-up to the next tag, but set cancel to true, in case there are
|
||||||
-- no more tags to bubble up to:
|
-- no more tags to bubble up to:
|
||||||
@ -333,20 +353,54 @@ function Renderer:_expr_map_callback(mode, lhs) -- {{{
|
|||||||
return cancel and '' or lhs
|
return cancel and '' or lhs
|
||||||
end -- }}}
|
end -- }}}
|
||||||
|
|
||||||
|
function Renderer:_on_text_changed()
|
||||||
|
--- @type integer, integer
|
||||||
|
local l, c = unpack(vim.api.nvim_win_get_cursor(0))
|
||||||
|
l = l - 1 -- make it actually 0-based
|
||||||
|
local pos_infos = self:get_pos_infos { l, c }
|
||||||
|
for _, pos_info in ipairs(pos_infos) do
|
||||||
|
local extmark_inf = pos_info.extmark
|
||||||
|
local tag = pos_info.tag
|
||||||
|
if tag.attributes.signal and getmetatable(tag.attributes.signal) == Signal then
|
||||||
|
--- @type u.Signal
|
||||||
|
local signal = tag.attributes.signal
|
||||||
|
|
||||||
|
local extmark =
|
||||||
|
vim.api.nvim_buf_get_extmark_by_id(self.bufnr, self.ns, extmark_inf.id, { details = true })
|
||||||
|
|
||||||
|
--- @type integer, integer, vim.api.keyset.extmark_details
|
||||||
|
local start_row0, start_col0, details = unpack(extmark)
|
||||||
|
local end_row0, end_col0 = details.end_row, details.end_col
|
||||||
|
|
||||||
|
if start_row0 == end_row0 and start_col0 == end_col0 then
|
||||||
|
-- Invalid extmark
|
||||||
|
else
|
||||||
|
local lines = vim.fn.getregion(
|
||||||
|
{ self.bufnr, start_row0 + 1, start_col0 + 1 },
|
||||||
|
{ self.bufnr, end_row0 + 1, end_col0 },
|
||||||
|
{ type = 'v' }
|
||||||
|
)
|
||||||
|
local text = table.concat(lines, '\n')
|
||||||
|
if text ~= signal:get() then signal:schedule_set(text) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- Returns pairs of extmarks and tags associate with said extmarks. The
|
--- Returns pairs of extmarks and tags associate with said extmarks. The
|
||||||
--- returned tags/extmarks are sorted smallest (innermost) to largest
|
--- returned tags/extmarks are sorted smallest (innermost) to largest
|
||||||
--- (outermost).
|
--- (outermost).
|
||||||
---
|
---
|
||||||
--- @private (private for now)
|
--- @private (private for now)
|
||||||
--- @param pos0 [number; number]
|
--- @param pos0 [number; number]
|
||||||
--- @return { extmark: RendererExtmark; tag: u.renderer.Tag; }[]
|
--- @return { extmark: u.renderer.RendererExtmark; tag: u.renderer.Tag; }[]
|
||||||
function Renderer:get_pos_infos(pos0) -- {{{
|
function Renderer:get_pos_infos(pos0) -- {{{
|
||||||
local cursor_line0, cursor_col0 = pos0[1], pos0[2]
|
local cursor_line0, cursor_col0 = pos0[1], pos0[2]
|
||||||
|
|
||||||
-- The cursor (block) occupies **two** extmark spaces: one for it's left
|
-- The cursor (block) occupies **two** extmark spaces: one for it's left
|
||||||
-- edge, and one for it's right. We need to do our own intersection test,
|
-- edge, and one for it's right. We need to do our own intersection test,
|
||||||
-- because the NeoVim API is over-inclusive in what it returns:
|
-- because the NeoVim API is over-inclusive in what it returns:
|
||||||
--- @type RendererExtmark[]
|
--- @type u.renderer.RendererExtmark[]
|
||||||
local intersecting_extmarks = vim
|
local intersecting_extmarks = vim
|
||||||
.iter(
|
.iter(
|
||||||
vim.api.nvim_buf_get_extmarks(
|
vim.api.nvim_buf_get_extmarks(
|
||||||
@ -357,7 +411,7 @@ function Renderer:get_pos_infos(pos0) -- {{{
|
|||||||
{ details = true, overlap = true }
|
{ details = true, overlap = true }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
--- @return RendererExtmark
|
--- @return u.renderer.RendererExtmark
|
||||||
:map(function(ext)
|
:map(function(ext)
|
||||||
--- @type number, number, number, { end_row?: number; end_col?: number }|nil
|
--- @type number, number, number, { end_row?: number; end_col?: number }|nil
|
||||||
local id, line0, col0, details = unpack(ext)
|
local id, line0, col0, details = unpack(ext)
|
||||||
@ -368,7 +422,7 @@ function Renderer:get_pos_infos(pos0) -- {{{
|
|||||||
end
|
end
|
||||||
return { id = id, start = start, stop = stop, opts = details }
|
return { id = id, start = start, stop = stop, opts = details }
|
||||||
end)
|
end)
|
||||||
--- @param ext RendererExtmark
|
--- @param ext u.renderer.RendererExtmark
|
||||||
:filter(function(ext)
|
:filter(function(ext)
|
||||||
if ext.stop[1] ~= nil and ext.stop[2] ~= nil then
|
if ext.stop[1] ~= nil and ext.stop[2] ~= nil then
|
||||||
return cursor_line0 >= ext.start[1]
|
return cursor_line0 >= ext.start[1]
|
||||||
@ -384,8 +438,8 @@ function Renderer:get_pos_infos(pos0) -- {{{
|
|||||||
-- Sort the tags into smallest (inner) to largest (outer):
|
-- Sort the tags into smallest (inner) to largest (outer):
|
||||||
table.sort(
|
table.sort(
|
||||||
intersecting_extmarks,
|
intersecting_extmarks,
|
||||||
--- @param x1 RendererExtmark
|
--- @param x1 u.renderer.RendererExtmark
|
||||||
--- @param x2 RendererExtmark
|
--- @param x2 u.renderer.RendererExtmark
|
||||||
function(x1, x2)
|
function(x1, x2)
|
||||||
if
|
if
|
||||||
x1.start[1] == x2.start[1]
|
x1.start[1] == x2.start[1]
|
||||||
@ -407,10 +461,10 @@ function Renderer:get_pos_infos(pos0) -- {{{
|
|||||||
-- created extmarks in self.curr.extmarks, which also has which tag each
|
-- created extmarks in self.curr.extmarks, which also has which tag each
|
||||||
-- extmark is associated with. Cross-reference with that list to get a list
|
-- extmark is associated with. Cross-reference with that list to get a list
|
||||||
-- of tags that we need to fire events for:
|
-- of tags that we need to fire events for:
|
||||||
--- @type { extmark: RendererExtmark; tag: u.renderer.Tag }[]
|
--- @type { extmark: u.renderer.RendererExtmark; tag: u.renderer.Tag }[]
|
||||||
local matching_tags = vim
|
local matching_tags = vim
|
||||||
.iter(intersecting_extmarks)
|
.iter(intersecting_extmarks)
|
||||||
--- @param ext RendererExtmark
|
--- @param ext u.renderer.RendererExtmark
|
||||||
:map(function(ext)
|
:map(function(ext)
|
||||||
for _, extmark_cache in ipairs(self.curr.extmarks) do
|
for _, extmark_cache in ipairs(self.curr.extmarks) do
|
||||||
if extmark_cache.id == ext.id then return { extmark = ext, tag = extmark_cache.tag } end
|
if extmark_cache.id == ext.id then return { extmark = ext, tag = extmark_cache.tag } end
|
||||||
@ -423,7 +477,7 @@ end -- }}}
|
|||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
-- TreeBuilder {{{
|
-- TreeBuilder {{{
|
||||||
--- @class u.TreeBuilder
|
--- @class u.renderer.TreeBuilder
|
||||||
--- @field private nodes u.renderer.Node[]
|
--- @field private nodes u.renderer.Node[]
|
||||||
local TreeBuilder = {}
|
local TreeBuilder = {}
|
||||||
TreeBuilder.__index = TreeBuilder
|
TreeBuilder.__index = TreeBuilder
|
||||||
@ -435,7 +489,7 @@ function TreeBuilder.new()
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- @param nodes u.renderer.Tree
|
--- @param nodes u.renderer.Tree
|
||||||
--- @return u.TreeBuilder
|
--- @return u.renderer.TreeBuilder
|
||||||
function TreeBuilder:put(nodes)
|
function TreeBuilder:put(nodes)
|
||||||
table.insert(self.nodes, nodes)
|
table.insert(self.nodes, nodes)
|
||||||
return self
|
return self
|
||||||
@ -444,7 +498,7 @@ end
|
|||||||
--- @param name string
|
--- @param name string
|
||||||
--- @param attributes? table<string, any>
|
--- @param attributes? table<string, any>
|
||||||
--- @param children? u.renderer.Node | u.renderer.Node[]
|
--- @param children? u.renderer.Node | u.renderer.Node[]
|
||||||
--- @return u.TreeBuilder
|
--- @return u.renderer.TreeBuilder
|
||||||
function TreeBuilder:put_h(name, attributes, children)
|
function TreeBuilder:put_h(name, attributes, children)
|
||||||
local tag = M.h(name, attributes, children)
|
local tag = M.h(name, attributes, children)
|
||||||
table.insert(self.nodes, tag)
|
table.insert(self.nodes, tag)
|
||||||
@ -452,7 +506,7 @@ function TreeBuilder:put_h(name, attributes, children)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- @param fn fun(TreeBuilder): any
|
--- @param fn fun(TreeBuilder): any
|
||||||
--- @return u.TreeBuilder
|
--- @return u.renderer.TreeBuilder
|
||||||
function TreeBuilder:nest(fn)
|
function TreeBuilder:nest(fn)
|
||||||
local nested_writer = TreeBuilder.new()
|
local nested_writer = TreeBuilder.new()
|
||||||
fn(nested_writer)
|
fn(nested_writer)
|
||||||
|
176
lua/u/utils.lua
176
lua/u/utils.lua
@ -4,17 +4,63 @@ local M = {}
|
|||||||
-- Types
|
-- Types
|
||||||
--
|
--
|
||||||
|
|
||||||
--- @alias QfItem { col: number, filename: string, kind: string, lnum: number, text: string }
|
--- @class u.utils.QfItem
|
||||||
--- @alias KeyMaps table<string, fun(): any | string> }
|
--- @field col number
|
||||||
-- luacheck: ignore
|
--- @field filename string
|
||||||
--- @alias RawCmdArgs { args: string; bang: boolean; count: number; fargs: string[]; line1: number; line2: number; mods: string; name: string; range: 0|1|2; reg: string; smods: any }
|
--- @field kind string
|
||||||
-- luacheck: ignore
|
--- @field lnum number
|
||||||
--- @alias CmdArgs { args: string; bang: boolean; count: number; fargs: string[]; line1: number; line2: number; mods: string; name: string; range: 0|1|2; reg: string; smods: any; info: u.Range|nil }
|
--- @field text string
|
||||||
|
|
||||||
|
--- @class u.utils.RawCmdArgs
|
||||||
|
--- @field args string
|
||||||
|
--- @field bang boolean
|
||||||
|
--- @field count number
|
||||||
|
--- @field fargs string[]
|
||||||
|
--- @field line1 number
|
||||||
|
--- @field line2 number
|
||||||
|
--- @field mods string
|
||||||
|
--- @field name string
|
||||||
|
--- @field range 0|1|2
|
||||||
|
--- @field reg string
|
||||||
|
--- @field smods any
|
||||||
|
|
||||||
|
--- @class u.utils.CmdArgs: u.utils.RawCmdArgs
|
||||||
|
--- @field info u.Range|nil
|
||||||
|
|
||||||
|
--- @class u.utils.UcmdArgs
|
||||||
|
--- @field nargs? 0|1|'*'|'?'|'+'
|
||||||
|
--- @field range? boolean|'%'|number
|
||||||
|
--- @field count? boolean|number
|
||||||
|
--- @field addr? string
|
||||||
|
--- @field completion? string
|
||||||
|
--- @field force? boolean
|
||||||
|
--- @field preview? fun(opts: u.utils.UcmdArgs, ns: integer, buf: integer):0|1|2
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Functions
|
||||||
|
--
|
||||||
|
|
||||||
|
--- Debug utility that prints a value and returns it unchanged.
|
||||||
|
--- Useful for debugging in the middle of expressions or function chains.
|
||||||
|
---
|
||||||
--- @generic T
|
--- @generic T
|
||||||
--- @param x `T`
|
--- @param x `T` The value to debug print
|
||||||
--- @param message? string
|
--- @param message? string Optional message to print alongside the value
|
||||||
--- @return T
|
--- @return T The original value, unchanged
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- -- Debug a value in the middle of a chain:
|
||||||
|
--- local result = some_function()
|
||||||
|
--- :map(utils.dbg) -- prints the intermediate value
|
||||||
|
--- :filter(predicate)
|
||||||
|
---
|
||||||
|
--- -- Debug with a custom message:
|
||||||
|
--- local config = utils.dbg(get_config(), "Current config:")
|
||||||
|
---
|
||||||
|
--- -- Debug return values:
|
||||||
|
--- return utils.dbg(calculate_result(), "Final result")
|
||||||
|
--- ```
|
||||||
function M.dbg(x, message)
|
function M.dbg(x, message)
|
||||||
local t = {}
|
local t = {}
|
||||||
if message ~= nil then table.insert(t, message) end
|
if message ~= nil then table.insert(t, message) end
|
||||||
@ -23,22 +69,37 @@ function M.dbg(x, message)
|
|||||||
return x
|
return x
|
||||||
end
|
end
|
||||||
|
|
||||||
--- A utility for creating user commands that also pre-computes useful information
|
--- Creates a user command with enhanced argument processing.
|
||||||
--- and attaches it to the arguments.
|
--- Automatically computes range information and attaches it as `args.info`.
|
||||||
---
|
---
|
||||||
|
--- @param name string The command name (without the leading colon)
|
||||||
|
--- @param cmd string | fun(args: u.utils.CmdArgs): any Command implementation
|
||||||
|
--- @param opts? u.utils.UcmdArgs Command options (nargs, range, etc.)
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
--- ```lua
|
--- ```lua
|
||||||
--- -- Example:
|
--- -- Create a command that works with visual selections:
|
||||||
--- ucmd('MyCmd', function(args)
|
--- utils.ucmd('MyCmd', function(args)
|
||||||
--- -- print the visually selected text:
|
--- -- Print the visually selected text:
|
||||||
--- vim.print(args.info:text())
|
--- vim.print(args.info:text())
|
||||||
--- -- or get the vtext as an array of lines:
|
--- -- Or get the selection as an array of lines:
|
||||||
--- vim.print(args.info:lines())
|
--- vim.print(args.info:lines())
|
||||||
--- end, { nargs = '*', range = true })
|
--- end, { nargs = '*', range = true })
|
||||||
|
---
|
||||||
|
--- -- Create a command that processes the current line:
|
||||||
|
--- utils.ucmd('ProcessLine', function(args)
|
||||||
|
--- local line_text = args.info:text()
|
||||||
|
--- -- Process the line...
|
||||||
|
--- end, { range = '%' })
|
||||||
|
---
|
||||||
|
--- -- Create a command with arguments:
|
||||||
|
--- utils.ucmd('SearchReplace', function(args)
|
||||||
|
--- local pattern, replacement = args.fargs[1], args.fargs[2]
|
||||||
|
--- local text = args.info:text()
|
||||||
|
--- -- Perform search and replace...
|
||||||
|
--- end, { nargs = 2, range = true })
|
||||||
--- ```
|
--- ```
|
||||||
--- @param name string
|
|
||||||
--- @param cmd string | fun(args: CmdArgs): any
|
|
||||||
-- luacheck: ignore
|
-- luacheck: ignore
|
||||||
--- @param opts? { nargs?: 0|1|'*'|'?'|'+'; range?: boolean|'%'|number; count?: boolean|number, addr?: string; completion?: string }
|
|
||||||
function M.ucmd(name, cmd, opts)
|
function M.ucmd(name, cmd, opts)
|
||||||
local Range = require 'u.range'
|
local Range = require 'u.range'
|
||||||
|
|
||||||
@ -50,20 +111,81 @@ function M.ucmd(name, cmd, opts)
|
|||||||
return cmd(args)
|
return cmd(args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
vim.api.nvim_create_user_command(name, cmd2, opts or {})
|
vim.api.nvim_create_user_command(name, cmd2, opts or {} --[[@as any]])
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param current_args RawCmdArgs
|
--- Creates command arguments for delegating from one command to another.
|
||||||
function M.create_forward_cmd_args(current_args)
|
--- Preserves all relevant context (range, modifiers, bang, etc.) when
|
||||||
local args = { args = current_args.fargs }
|
--- implementing a derived command in terms of a base command.
|
||||||
if current_args.range == 1 then
|
---
|
||||||
args.range = { current_args.line1 }
|
--- @param current_args vim.api.keyset.create_user_command.command_args|u.utils.RawCmdArgs The arguments from the current command
|
||||||
elseif current_args.range == 2 then
|
--- @return vim.api.keyset.cmd Arguments suitable for vim.cmd() calls
|
||||||
args.range = { current_args.line1, current_args.line2 }
|
---
|
||||||
end
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- -- Implement :MyEdit in terms of :edit, preserving all context:
|
||||||
|
--- utils.ucmd('MyEdit', function(args)
|
||||||
|
--- local delegated_args = utils.create_delegated_cmd_args(args)
|
||||||
|
--- -- Add custom logic here...
|
||||||
|
--- vim.cmd.edit(delegated_args)
|
||||||
|
--- end, { nargs = '*', range = true, bang = true })
|
||||||
|
---
|
||||||
|
--- -- Implement :MySubstitute that delegates to :substitute:
|
||||||
|
--- utils.ucmd('MySubstitute', function(args)
|
||||||
|
--- -- Pre-process arguments
|
||||||
|
--- local pattern = preprocess_pattern(args.fargs[1])
|
||||||
|
---
|
||||||
|
--- local delegated_args = utils.create_delegated_cmd_args(args)
|
||||||
|
--- delegated_args.args = { pattern, args.fargs[2] }
|
||||||
|
---
|
||||||
|
--- vim.cmd.substitute(delegated_args)
|
||||||
|
--- end, { nargs = 2, range = true, bang = true })
|
||||||
|
--- ```
|
||||||
|
function M.create_delegated_cmd_args(current_args)
|
||||||
|
--- @type vim.api.keyset.cmd
|
||||||
|
local args = {
|
||||||
|
range = current_args.range == 1 and { current_args.line1 }
|
||||||
|
or current_args.range == 2 and { current_args.line1, current_args.line2 }
|
||||||
|
or nil,
|
||||||
|
count = current_args.count ~= -1 and current_args.count or nil,
|
||||||
|
reg = current_args.reg ~= '' and current_args.reg or nil,
|
||||||
|
bang = current_args.bang or nil,
|
||||||
|
args = #current_args.fargs > 0 and current_args.fargs or nil,
|
||||||
|
mods = current_args.smods,
|
||||||
|
}
|
||||||
return args
|
return args
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Gets the current editor dimensions.
|
||||||
|
--- Useful for positioning floating windows or calculating layout sizes.
|
||||||
|
---
|
||||||
|
--- @return { width: number, height: number } The editor dimensions in columns and lines
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- -- Center a floating window:
|
||||||
|
--- local dims = utils.get_editor_dimensions()
|
||||||
|
--- local win_width = 80
|
||||||
|
--- local win_height = 20
|
||||||
|
--- local col = math.floor((dims.width - win_width) / 2)
|
||||||
|
--- local row = math.floor((dims.height - win_height) / 2)
|
||||||
|
---
|
||||||
|
--- vim.api.nvim_open_win(bufnr, true, {
|
||||||
|
--- relative = 'editor',
|
||||||
|
--- width = win_width,
|
||||||
|
--- height = win_height,
|
||||||
|
--- col = col,
|
||||||
|
--- row = row,
|
||||||
|
--- })
|
||||||
|
---
|
||||||
|
--- -- Check if editor is wide enough for side-by-side layout:
|
||||||
|
--- local dims = utils.get_editor_dimensions()
|
||||||
|
--- if dims.width >= 160 then
|
||||||
|
--- -- Use side-by-side layout
|
||||||
|
--- else
|
||||||
|
--- -- Use stacked layout
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
function M.get_editor_dimensions() return { width = vim.go.columns, height = vim.go.lines } end
|
function M.get_editor_dimensions() return { width = vim.go.columns, height = vim.go.lines } end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
Loading…
x
Reference in New Issue
Block a user