164 lines
4.2 KiB
Lua
164 lines
4.2 KiB
Lua
local NS = vim.api.nvim_create_namespace("ow.lsp.codelens")
|
|
|
|
---@alias ow.lsp.codelens.Position "eol" | "right_align" | "inline" | "above"
|
|
|
|
---@type ow.lsp.codelens.Position
|
|
local position = "above"
|
|
local separator = " | "
|
|
|
|
---@class ow.lsp.CodeLens : lsp.CodeLens
|
|
---@field command lsp.Command
|
|
|
|
---@class ow.lsp.codelens.Row
|
|
---@field row integer
|
|
---@field lenses ow.lsp.CodeLens[]
|
|
---@field pending integer
|
|
local Row = {}
|
|
Row.__index = Row
|
|
|
|
---@param row integer
|
|
---@return ow.lsp.codelens.Row
|
|
function Row.new(row)
|
|
return setmetatable({ row = row, lenses = {}, pending = 0 }, Row)
|
|
end
|
|
|
|
---@param lens ow.lsp.CodeLens
|
|
function Row:add(lens)
|
|
table.insert(self.lenses, lens)
|
|
end
|
|
|
|
function Row:expect()
|
|
self.pending = self.pending + 1
|
|
end
|
|
|
|
---@param lens? lsp.CodeLens
|
|
function Row:resolve(lens)
|
|
self.pending = self.pending - 1
|
|
if lens and lens.command then
|
|
self:add(lens)
|
|
end
|
|
end
|
|
|
|
---@return boolean
|
|
function Row:is_ready()
|
|
return self.pending == 0 and #self.lenses > 0
|
|
end
|
|
|
|
---@param buf integer
|
|
function Row:render(buf)
|
|
if not self:is_ready() or not vim.api.nvim_buf_is_valid(buf) then
|
|
return
|
|
end
|
|
|
|
table.sort(self.lenses, function(a, b)
|
|
return a.range.start.character < b.range.start.character
|
|
end)
|
|
local parts = {}
|
|
for i, lens in ipairs(self.lenses) do
|
|
table.insert(parts, { lens.command.title, "LspCodeLens" })
|
|
if i < #self.lenses then
|
|
table.insert(parts, { separator, "LspCodeLensSeparator" })
|
|
end
|
|
end
|
|
|
|
---@type vim.api.keyset.set_extmark
|
|
local opts
|
|
local col
|
|
if position == "above" then
|
|
local line = vim.api.nvim_buf_get_lines(
|
|
buf,
|
|
self.row,
|
|
self.row + 1,
|
|
false
|
|
)[1] or ""
|
|
local indent = line:match("^%s*") or ""
|
|
local chunks = parts
|
|
if indent ~= "" then
|
|
chunks = { { indent, "LspCodeLensSeparator" } }
|
|
vim.list_extend(chunks, parts)
|
|
end
|
|
col = 0
|
|
opts = {
|
|
virt_lines = { chunks },
|
|
virt_lines_above = true,
|
|
hl_mode = "combine",
|
|
}
|
|
else
|
|
local first = assert(self.lenses[1])
|
|
col = position == "inline" and first.range.start.character or 0
|
|
opts = {
|
|
virt_text = parts,
|
|
virt_text_pos = position,
|
|
hl_mode = "combine",
|
|
}
|
|
end
|
|
|
|
local marks = vim.api.nvim_buf_get_extmarks(
|
|
buf,
|
|
NS,
|
|
{ self.row, 0 },
|
|
{ self.row, -1 },
|
|
{ details = true }
|
|
)
|
|
---@type {
|
|
--- id: integer,
|
|
--- col: integer,
|
|
--- details: vim.api.keyset.extmark_details?
|
|
---}
|
|
local primary
|
|
for i, mark in ipairs(marks) do
|
|
if i == 1 then
|
|
primary = { id = mark[1], col = mark[3], details = mark[4] }
|
|
else
|
|
vim.api.nvim_buf_del_extmark(buf, NS, mark[1])
|
|
end
|
|
end
|
|
|
|
if primary and primary.col == col and primary.details then
|
|
local d = primary.details
|
|
local same = opts.virt_lines
|
|
and vim.deep_equal(d.virt_lines, opts.virt_lines)
|
|
and d.virt_lines_above == opts.virt_lines_above
|
|
or not opts.virt_lines
|
|
and d.virt_text_pos == opts.virt_text_pos
|
|
and vim.deep_equal(d.virt_text, opts.virt_text)
|
|
if same then
|
|
return
|
|
end
|
|
end
|
|
|
|
opts.id = primary and primary.id or nil
|
|
vim.api.nvim_buf_set_extmark(buf, NS, self.row, col, opts)
|
|
end
|
|
|
|
---@param buf integer
|
|
function Row.clear_all(buf)
|
|
vim.api.nvim_buf_clear_namespace(buf, NS, 0, -1)
|
|
end
|
|
|
|
---@param buf integer
|
|
---@param rows table<integer, ow.lsp.codelens.Row>
|
|
function Row.prune(buf, rows)
|
|
for _, mark in ipairs(vim.api.nvim_buf_get_extmarks(buf, NS, 0, -1, {})) do
|
|
if not rows[mark[2]] then
|
|
vim.api.nvim_buf_del_extmark(buf, NS, mark[1])
|
|
end
|
|
end
|
|
end
|
|
|
|
---@class ow.lsp.codelens.row.ConfigureOpts
|
|
---@field position? ow.lsp.codelens.Position
|
|
---@field separator? string
|
|
|
|
---@param opts ow.lsp.codelens.row.ConfigureOpts
|
|
function Row.configure(opts)
|
|
if opts.position ~= nil then
|
|
position = opts.position
|
|
end
|
|
if opts.separator ~= nil then
|
|
separator = opts.separator
|
|
end
|
|
end
|
|
|
|
return Row
|