refactor(codelens): split into modules and add cancellation
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
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.Row
|
||||
---@field row integer
|
||||
---@field lenses 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 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 then
|
||||
table.insert(self.lenses, 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
|
||||
|
||||
local col, opts
|
||||
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
|
||||
col = position == "inline" and self.lenses[1].range.start.character or 0
|
||||
opts = {
|
||||
virt_text = parts,
|
||||
virt_text_pos = position,
|
||||
hl_mode = "combine",
|
||||
}
|
||||
end
|
||||
|
||||
-- One extmark per row. Extmarks auto-shift with edits, so multiple from
|
||||
-- prior refreshes can end up on the same row; pick the first and drop
|
||||
-- the rest, then reuse its id for an atomic update.
|
||||
local marks = vim.api.nvim_buf_get_extmarks(
|
||||
buf,
|
||||
NS,
|
||||
{ self.row, 0 },
|
||||
{ self.row, -1 },
|
||||
{ details = true }
|
||||
)
|
||||
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 --[[@as vim.api.keyset.extmark_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
|
||||
Reference in New Issue
Block a user