Initial commit
This commit is contained in:
commit
209ba130c0
4852 changed files with 1517959 additions and 0 deletions
580
.config/lite-xl/plugins/lsp/listbox.lua
Normal file
580
.config/lite-xl/plugins/lsp/listbox.lua
Normal file
|
|
@ -0,0 +1,580 @@
|
|||
-- A configurable listbox that can be used as tooltip, selection box and
|
||||
-- selection box with fuzzy search, this may change in the future.
|
||||
--
|
||||
-- @note This code is a readaptation of autocomplete plugin from rxi :)
|
||||
--
|
||||
-- TODO implement select box with fuzzy search
|
||||
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local util = require "plugins.lsp.util"
|
||||
local RootView = require "core.rootview"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
---@class lsp.listbox.item
|
||||
---@field text string
|
||||
---@field info string
|
||||
---@field on_draw fun(item:lsp.listbox.item, x:number, y:number, calc_only?:boolean):number
|
||||
|
||||
---@alias lsp.listbox.callback fun(doc: core.doc, item: lsp.listbox.item)
|
||||
|
||||
---@class lsp.listbox.signature_param
|
||||
---@field label string
|
||||
|
||||
---@class lsp.listbox.signature
|
||||
---@field label string
|
||||
---@field activeParameter? integer
|
||||
---@field activeSignature? integer
|
||||
---@field parameters lsp.listbox.signature_param[]
|
||||
|
||||
---@class lsp.listbox.signature_list
|
||||
---@field activeParameter? integer
|
||||
---@field activeSignature? integer
|
||||
---@field signatures lsp.listbox.signature[]
|
||||
|
||||
---@class lsp.listbox.position
|
||||
---@field line integer
|
||||
---@field col integer
|
||||
|
||||
---@class lsp.listbox
|
||||
local listbox = {}
|
||||
|
||||
---@class lsp.listbox.settings
|
||||
---@field items lsp.listbox.item[]
|
||||
---@field shown_items lsp.listbox.item[]
|
||||
---@field selected_item_idx integer
|
||||
---@field show_items_count boolean
|
||||
---@field max_height integer
|
||||
---@field active_view core.docview | nil
|
||||
---@field line integer | nil
|
||||
---@field col integer | nil
|
||||
---@field last_line integer | nil
|
||||
---@field last_col integer | nil
|
||||
---@field callback lsp.listbox.callback | nil
|
||||
---@field is_list boolean
|
||||
---@field has_fuzzy_search boolean
|
||||
---@field above_text boolean
|
||||
local settings = {
|
||||
items = {},
|
||||
shown_items = {},
|
||||
selected_item_idx = 1,
|
||||
show_items_count = false,
|
||||
max_height = 6,
|
||||
active_view = nil,
|
||||
line = nil,
|
||||
col = nil,
|
||||
last_line = nil,
|
||||
last_col = nil,
|
||||
callback = nil,
|
||||
is_list = false,
|
||||
has_fuzzy_search = false,
|
||||
above_text = false,
|
||||
}
|
||||
|
||||
local mt = { __tostring = function(t) return t.text end }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Private functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
---@return core.docview | nil
|
||||
local function get_active_view()
|
||||
if getmetatable(core.active_view) == DocView then
|
||||
return core.active_view
|
||||
end
|
||||
end
|
||||
|
||||
---@param active_view core.docview
|
||||
---@return number x
|
||||
---@return number y
|
||||
---@return number width
|
||||
---@return number height
|
||||
local function get_suggestions_rect(active_view)
|
||||
if #settings.shown_items == 0 then
|
||||
listbox.hide()
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
|
||||
local line, col
|
||||
if settings.line then
|
||||
line, col = settings.line, settings.col
|
||||
else
|
||||
line, col = active_view.doc:get_selection()
|
||||
end
|
||||
|
||||
-- Validate line against current view because there can be cases
|
||||
-- when user rapidly switches between tabs causing the deferred draw
|
||||
-- to be called late and the current document view already changed.
|
||||
if line > #active_view.doc.lines then
|
||||
listbox.hide()
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
|
||||
local x, y = active_view:get_line_screen_position(line)
|
||||
|
||||
-- This function causes tokenizer to fail if given line is greater than
|
||||
-- the amount of lines the document holds, so validation above is needed.
|
||||
x = x + active_view:get_col_x_offset(line, col)
|
||||
|
||||
local padding_x = style.padding.x
|
||||
local padding_y = style.padding.y
|
||||
|
||||
if settings.above_text and line > 1 then
|
||||
y = y - active_view:get_line_height() - style.padding.y
|
||||
else
|
||||
y = y + active_view:get_line_height() + style.padding.y
|
||||
end
|
||||
|
||||
local font = settings.is_list and active_view:get_font() or style.font
|
||||
local text_height = font:get_height()
|
||||
|
||||
local max_width = 0
|
||||
for _, item in ipairs(settings.shown_items) do
|
||||
local w = 0
|
||||
if item.on_draw then
|
||||
w = item.on_draw(item, 0, 0, true)
|
||||
else
|
||||
w = font:get_width(item.text)
|
||||
if item.info then
|
||||
w = w + style.font:get_width(item.info) + style.padding.x
|
||||
end
|
||||
end
|
||||
max_width = math.max(max_width, w)
|
||||
end
|
||||
|
||||
local max_items = #settings.shown_items
|
||||
if settings.is_list and max_items > settings.max_height then
|
||||
max_items = settings.max_height
|
||||
end
|
||||
|
||||
-- additional line to display total items
|
||||
if settings.show_items_count then
|
||||
max_items = max_items + 1
|
||||
end
|
||||
|
||||
if max_width < 150 then
|
||||
max_width = 150
|
||||
end
|
||||
|
||||
local height = max_items * (text_height + (padding_y/4)) + (padding_y*2)
|
||||
local width = max_width + padding_x * 2
|
||||
|
||||
x = x - padding_x
|
||||
y = y - padding_y
|
||||
|
||||
local win_w = system.get_window_size()
|
||||
if (width/win_w*100) >= 85 and (width+style.padding.x*4) < win_w then
|
||||
x = win_w - width - style.padding.x*2
|
||||
elseif width > (win_w - x) then
|
||||
x = x - (width - (win_w - x))
|
||||
if x < 0 then
|
||||
x = 0
|
||||
end
|
||||
end
|
||||
|
||||
return x, y, width, height
|
||||
end
|
||||
|
||||
---@param av core.docview
|
||||
local function draw_listbox(av)
|
||||
if #settings.shown_items <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- draw background rect
|
||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||
|
||||
-- draw border
|
||||
if not settings.is_list then
|
||||
local border_width = 1
|
||||
renderer.draw_rect(
|
||||
rx - border_width,
|
||||
ry - border_width,
|
||||
rw + (border_width * 2),
|
||||
rh + (border_width * 2),
|
||||
style.divider
|
||||
)
|
||||
end
|
||||
|
||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||
|
||||
local padding_x = style.padding.x
|
||||
local padding_y = style.padding.y
|
||||
|
||||
-- draw text
|
||||
local font = settings.is_list and av:get_font() or style.font
|
||||
local line_height = font:get_height() + (padding_y / 4)
|
||||
local y = ry + padding_y
|
||||
|
||||
local max_height = settings.max_height
|
||||
|
||||
local show_count = (
|
||||
#settings.shown_items <= max_height or not settings.is_list
|
||||
) and
|
||||
#settings.shown_items or max_height
|
||||
|
||||
local start_index = settings.selected_item_idx > max_height and
|
||||
(settings.selected_item_idx-(max_height-1)) or 1
|
||||
|
||||
for i=start_index, start_index+show_count-1, 1 do
|
||||
if not settings.shown_items[i] then
|
||||
break
|
||||
end
|
||||
|
||||
local item = settings.shown_items[i]
|
||||
|
||||
if item.on_draw then
|
||||
item.on_draw(item, rx + padding_x, y)
|
||||
else
|
||||
local color = (i == settings.selected_item_idx and settings.is_list) and
|
||||
style.accent or style.text
|
||||
|
||||
common.draw_text(
|
||||
font, color, item.text, "left",
|
||||
rx + padding_x, y, rw, line_height
|
||||
)
|
||||
|
||||
if item.info then
|
||||
color = (i == settings.selected_item_idx and settings.is_list) and
|
||||
style.text or style.dim
|
||||
|
||||
common.draw_text(
|
||||
style.font, color, item.info, "right",
|
||||
rx, y, rw - padding_x, line_height
|
||||
)
|
||||
end
|
||||
end
|
||||
y = y + line_height
|
||||
end
|
||||
|
||||
if settings.show_items_count then
|
||||
renderer.draw_rect(rx, y, rw, 2, style.caret)
|
||||
renderer.draw_rect(rx, y+2, rw, line_height, style.background)
|
||||
common.draw_text(
|
||||
style.font,
|
||||
style.accent,
|
||||
"Items",
|
||||
"left",
|
||||
rx + padding_x, y, rw, line_height
|
||||
)
|
||||
common.draw_text(
|
||||
style.font,
|
||||
style.accent,
|
||||
tostring(settings.selected_item_idx) .. "/" .. tostring(#settings.shown_items),
|
||||
"right",
|
||||
rx, y, rw - padding_x, line_height
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
---Set the document position where the listbox will be draw.
|
||||
---@param position? lsp.listbox.position
|
||||
local function set_position(position)
|
||||
if type(position) == "table" then
|
||||
settings.line = position.line
|
||||
settings.col = position.col
|
||||
else
|
||||
settings.line = nil
|
||||
settings.col = nil
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Public functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
---@param elements lsp.listbox.item[]
|
||||
function listbox.add(elements)
|
||||
if type(elements) == "table" and #elements > 0 then
|
||||
local items = {}
|
||||
for _, element in pairs(elements) do
|
||||
table.insert(items, setmetatable(element, mt))
|
||||
end
|
||||
settings.items = items
|
||||
end
|
||||
end
|
||||
|
||||
function listbox.clear()
|
||||
settings.items = {}
|
||||
settings.selected_item_idx = 1
|
||||
settings.shown_items = {}
|
||||
settings.line = nil
|
||||
settings.col = nil
|
||||
end
|
||||
|
||||
---@param element lsp.listbox.item
|
||||
function listbox.append(element)
|
||||
table.insert(settings.items, setmetatable(element, mt))
|
||||
end
|
||||
|
||||
function listbox.hide()
|
||||
settings.active_view = nil
|
||||
settings.line = nil
|
||||
settings.col = nil
|
||||
settings.selected_item_idx = 1
|
||||
settings.shown_items = {}
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
---@param is_list? boolean
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show(is_list, position)
|
||||
set_position(position)
|
||||
|
||||
local active_view = get_active_view()
|
||||
if active_view then
|
||||
settings.active_view = active_view
|
||||
settings.last_line, settings.last_col = active_view.doc:get_selection()
|
||||
if settings.items and #settings.items > 0 then
|
||||
settings.is_list = is_list or false
|
||||
settings.shown_items = settings.items
|
||||
end
|
||||
end
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show_text(text, position)
|
||||
if text and type("text") == "string" then
|
||||
local win_w = system.get_window_size() - style.padding.x * 6
|
||||
text = util.wrap_text(text, style.font, win_w)
|
||||
|
||||
local items = {}
|
||||
for result in string.gmatch(text.."\n", "(.-)\n") do
|
||||
table.insert(items, {text = result})
|
||||
end
|
||||
listbox.add(items)
|
||||
end
|
||||
|
||||
listbox.show(false, position)
|
||||
end
|
||||
|
||||
---@param items lsp.listbox.item[]
|
||||
---@param callback lsp.listbox.callback
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show_list(items, callback, position)
|
||||
listbox.add(items)
|
||||
|
||||
if callback then
|
||||
settings.callback = callback
|
||||
end
|
||||
|
||||
listbox.show(true, position)
|
||||
end
|
||||
|
||||
---@param signatures lsp.listbox.signature_list
|
||||
---@param position? lsp.listbox.position
|
||||
function listbox.show_signatures(signatures, position)
|
||||
local active_parameter = nil
|
||||
local active_signature = nil
|
||||
|
||||
if signatures.activeParameter then
|
||||
active_parameter = signatures.activeParameter + 1
|
||||
end
|
||||
|
||||
if signatures.activeSignature then
|
||||
active_signature = signatures.activeSignature + 1
|
||||
end
|
||||
|
||||
local signatures_count = #signatures.signatures
|
||||
|
||||
local items = {}
|
||||
for index, signature in ipairs(signatures.signatures) do
|
||||
table.insert(items, {
|
||||
text = signature.label,
|
||||
signature = signature,
|
||||
on_draw = function(item, x, y, calc_only)
|
||||
local width = 0
|
||||
local height = style.font:get_height()
|
||||
|
||||
if item.signature.parameters then
|
||||
if signatures_count > 1 then
|
||||
if index == active_signature then
|
||||
width = style.font:get_width("> ")
|
||||
else
|
||||
width = style.font:get_width("> ")
|
||||
x = x + style.font:get_width("> ")
|
||||
end
|
||||
end
|
||||
|
||||
width = width
|
||||
+ style.font:get_width("(")
|
||||
+ style.font:get_width(")")
|
||||
|
||||
if not calc_only then
|
||||
if signatures_count > 1 and index == active_signature then
|
||||
x = renderer.draw_text(style.font, "> ", x, y, style.caret)
|
||||
end
|
||||
x = renderer.draw_text(style.font, "(", x, y, style.text)
|
||||
end
|
||||
|
||||
local params_count = #item.signature.parameters
|
||||
for pindex, param in ipairs(item.signature.parameters) do
|
||||
local label = ""
|
||||
if type(param.label) == "table" then
|
||||
label = signature.label:sub(param.label[1]+1, param.label[2])
|
||||
else
|
||||
label = param.label
|
||||
end
|
||||
if label and pindex ~= params_count then
|
||||
label = label .. ", "
|
||||
end
|
||||
width = width + style.font:get_width(label)
|
||||
if not calc_only then
|
||||
local color = style.text
|
||||
if
|
||||
(
|
||||
signature.activeParameter
|
||||
and
|
||||
(signature.activeParameter + 1) == pindex
|
||||
)
|
||||
or
|
||||
(index == active_signature and active_parameter == pindex)
|
||||
then
|
||||
color = style.accent
|
||||
end
|
||||
x = renderer.draw_text(
|
||||
style.font,
|
||||
label,
|
||||
x, y,
|
||||
color
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if not calc_only then
|
||||
renderer.draw_text(style.font, ")", x, y, style.text)
|
||||
end
|
||||
else
|
||||
width = style.font:get_width(item.signature.label)
|
||||
if not calc_only then
|
||||
renderer.draw_text(
|
||||
style.font,
|
||||
item.signature.label,
|
||||
x, y,
|
||||
style.text
|
||||
)
|
||||
end
|
||||
end
|
||||
return width, width > 0 and height or 0
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
listbox.add(items)
|
||||
|
||||
listbox.show(false, position)
|
||||
end
|
||||
|
||||
function listbox.toggle_above(enable)
|
||||
if enable then
|
||||
settings.above_text = true
|
||||
else
|
||||
settings.above_text = false
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Patch event logic into RootView
|
||||
--------------------------------------------------------------------------------
|
||||
local root_view_update = RootView.update
|
||||
local root_view_draw = RootView.draw
|
||||
|
||||
RootView.update = function(...)
|
||||
root_view_update(...)
|
||||
|
||||
if not settings.active_view then return end
|
||||
|
||||
local active_view = get_active_view()
|
||||
if active_view then
|
||||
-- reset suggestions if caret was moved or not same active view
|
||||
local line, col = active_view.doc:get_selection()
|
||||
if
|
||||
settings.active_view ~= active_view
|
||||
or
|
||||
line ~= settings.last_line or col ~= settings.last_col
|
||||
then
|
||||
listbox.hide()
|
||||
end
|
||||
else
|
||||
listbox.hide()
|
||||
end
|
||||
end
|
||||
|
||||
RootView.draw = function(...)
|
||||
if settings.active_view then
|
||||
local active_view = get_active_view()
|
||||
if
|
||||
active_view and settings.active_view == active_view
|
||||
and
|
||||
#settings.shown_items > 0
|
||||
then
|
||||
-- draw suggestions box after everything else
|
||||
core.root_view:defer_draw(draw_listbox, active_view)
|
||||
end
|
||||
end
|
||||
root_view_draw(...)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Commands
|
||||
--------------------------------------------------------------------------------
|
||||
local function predicate()
|
||||
local av = get_active_view()
|
||||
return av and settings.active_view and #settings.shown_items > 0, av
|
||||
end
|
||||
|
||||
command.add(predicate, {
|
||||
["listbox:select"] = function(av)
|
||||
---@cast av core.docview
|
||||
if settings.is_list then
|
||||
local doc = av.doc
|
||||
local item = settings.shown_items[settings.selected_item_idx]
|
||||
|
||||
if settings.callback then
|
||||
settings.callback(doc, item)
|
||||
end
|
||||
|
||||
listbox.hide()
|
||||
end
|
||||
end,
|
||||
|
||||
["listbox:previous"] = function()
|
||||
if settings.is_list then
|
||||
settings.selected_item_idx = math.max(settings.selected_item_idx - 1, 1)
|
||||
else
|
||||
listbox.hide()
|
||||
end
|
||||
end,
|
||||
|
||||
["listbox:next"] = function()
|
||||
if settings.is_list then
|
||||
settings.selected_item_idx = math.min(
|
||||
settings.selected_item_idx + 1, #settings.shown_items
|
||||
)
|
||||
else
|
||||
listbox.hide()
|
||||
end
|
||||
end,
|
||||
|
||||
["listbox:cancel"] = function()
|
||||
listbox.hide()
|
||||
end,
|
||||
})
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Keymaps
|
||||
--------------------------------------------------------------------------------
|
||||
keymap.add {
|
||||
["tab"] = "listbox:select",
|
||||
["up"] = "listbox:previous",
|
||||
["down"] = "listbox:next",
|
||||
["escape"] = "listbox:cancel",
|
||||
}
|
||||
|
||||
|
||||
return listbox
|
||||
Loading…
Add table
Add a link
Reference in a new issue