Autocomplete plugin improvements (#1519)
* Add icons support to autocomplete plugin * Removed redundant flag check * Added support for non syntax colors * Assert if color name not in style.syntax * Autocomplete plugin improvements * Support suggestion symbols scoping - global: all open documents - local: current document - related: all open documents with same syntax - none: language syntax symbols only * Register style.syntax[] entries as icons * Other related fixes
This commit is contained in:
parent
3f28557aeb
commit
215b8daef7
|
@ -10,6 +10,10 @@ local RootView = require "core.rootview"
|
|||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
---Symbols cache of all open documents
|
||||
---@type table<core.doc, table>
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
||||
config.plugins.autocomplete = common.merge({
|
||||
-- Amount of characters that need to be written for autocomplete
|
||||
min_len = 3,
|
||||
|
@ -19,8 +23,16 @@ config.plugins.autocomplete = common.merge({
|
|||
max_suggestions = 100,
|
||||
-- Maximum amount of symbols to cache per document
|
||||
max_symbols = 4000,
|
||||
-- Which symbols to show on the suggestions list: global, local, related, none
|
||||
suggestions_scope = "global",
|
||||
-- Font size of the description box
|
||||
desc_font_size = 12,
|
||||
-- Do not show the icons associated to the suggestions
|
||||
hide_icons = false,
|
||||
-- Position where icons will be displayed on the suggestions list
|
||||
icon_position = "left",
|
||||
-- Do not show the additional information related to a suggestion
|
||||
hide_info = false,
|
||||
-- The config specification used by gui generators
|
||||
config_spec = {
|
||||
name = "Autocomplete",
|
||||
|
@ -60,6 +72,26 @@ config.plugins.autocomplete = common.merge({
|
|||
min = 1000,
|
||||
max = 10000
|
||||
},
|
||||
{
|
||||
label = "Suggestions Scope",
|
||||
description = "Which symbols to show on the suggestions list.",
|
||||
path = "suggestions_scope",
|
||||
type = "selection",
|
||||
default = "global",
|
||||
values = {
|
||||
{"All Documents", "global"},
|
||||
{"Current Document", "local"},
|
||||
{"Related Documents", "related"},
|
||||
{"Known Symbols", "none"}
|
||||
},
|
||||
on_apply = function(value)
|
||||
if value == "global" then
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if cache[doc] then cache[doc] = nil end
|
||||
end
|
||||
end
|
||||
end
|
||||
},
|
||||
{
|
||||
label = "Description Font Size",
|
||||
description = "Font size of the description box.",
|
||||
|
@ -67,6 +99,31 @@ config.plugins.autocomplete = common.merge({
|
|||
type = "number",
|
||||
default = 12,
|
||||
min = 8
|
||||
},
|
||||
{
|
||||
label = "Hide Icons",
|
||||
description = "Do not show icons on the suggestions list.",
|
||||
path = "hide_icons",
|
||||
type = "toggle",
|
||||
default = false
|
||||
},
|
||||
{
|
||||
label = "Icons Position",
|
||||
description = "Position to display icons on the suggestions list.",
|
||||
path = "icon_position",
|
||||
type = "selection",
|
||||
default = "left",
|
||||
values = {
|
||||
{"Left", "left"},
|
||||
{"Right", "Right"}
|
||||
}
|
||||
},
|
||||
{
|
||||
label = "Hide Items Info",
|
||||
description = "Do not show the additional info related to each suggestion.",
|
||||
path = "hide_info",
|
||||
type = "toggle",
|
||||
default = false
|
||||
}
|
||||
}
|
||||
}, config.plugins.autocomplete)
|
||||
|
@ -76,6 +133,7 @@ local autocomplete = {}
|
|||
autocomplete.map = {}
|
||||
autocomplete.map_manually = {}
|
||||
autocomplete.on_close = nil
|
||||
autocomplete.icons = {}
|
||||
|
||||
-- Flag that indicates if the autocomplete box was manually triggered
|
||||
-- with the autocomplete.complete() function to prevent the suggestions
|
||||
|
@ -95,6 +153,7 @@ function autocomplete.add(t, manually_triggered)
|
|||
{
|
||||
text = text,
|
||||
info = info.info,
|
||||
icon = info.icon, -- Name of icon to show
|
||||
desc = info.desc, -- Description shown on item selected
|
||||
onhover = info.onhover, -- A callback called once when item is hovered
|
||||
onselect = info.onselect, -- A callback called when item is selected
|
||||
|
@ -119,28 +178,35 @@ end
|
|||
--
|
||||
-- Thread that scans open document symbols and cache them
|
||||
--
|
||||
local max_symbols = config.plugins.autocomplete.max_symbols
|
||||
local global_symbols = {}
|
||||
|
||||
core.add_thread(function()
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
||||
local function get_syntax_symbols(symbols, doc)
|
||||
if doc.syntax then
|
||||
for sym in pairs(doc.syntax.symbols) do
|
||||
symbols[sym] = true
|
||||
local function load_syntax_symbols(doc)
|
||||
if doc.syntax and not autocomplete.map["language_"..doc.syntax.name] then
|
||||
local symbols = {
|
||||
name = "language_"..doc.syntax.name,
|
||||
files = doc.syntax.files,
|
||||
items = {}
|
||||
}
|
||||
for name, type in pairs(doc.syntax.symbols) do
|
||||
symbols.items[name] = type
|
||||
end
|
||||
autocomplete.add(symbols)
|
||||
return symbols.items
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
local function get_symbols(doc)
|
||||
local s = {}
|
||||
get_syntax_symbols(s, doc)
|
||||
local syntax_symbols = load_syntax_symbols(doc)
|
||||
local max_symbols = config.plugins.autocomplete.max_symbols
|
||||
if doc.disable_symbols then return s end
|
||||
local i = 1
|
||||
local symbols_count = 0
|
||||
while i <= #doc.lines do
|
||||
for sym in doc.lines[i]:gmatch(config.symbol_pattern) do
|
||||
if not s[sym] then
|
||||
if not s[sym] and not syntax_symbols[sym] then
|
||||
symbols_count = symbols_count + 1
|
||||
if symbols_count > max_symbols then
|
||||
s = nil
|
||||
|
@ -186,14 +252,18 @@ core.add_thread(function()
|
|||
}
|
||||
end
|
||||
-- update symbol set with doc's symbol set
|
||||
for sym in pairs(cache[doc].symbols) do
|
||||
symbols[sym] = true
|
||||
if config.plugins.autocomplete.suggestions_scope == "global" then
|
||||
for sym in pairs(cache[doc].symbols) do
|
||||
symbols[sym] = true
|
||||
end
|
||||
end
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
-- update symbols list
|
||||
autocomplete.add { name = "open-docs", items = symbols }
|
||||
-- update global symbols list
|
||||
if config.plugins.autocomplete.suggestions_scope == "global" then
|
||||
global_symbols = symbols
|
||||
end
|
||||
|
||||
-- wait for next scan
|
||||
local valid = true
|
||||
|
@ -240,12 +310,50 @@ local function update_suggestions()
|
|||
map = autocomplete.map_manually
|
||||
end
|
||||
|
||||
local assigned_sym = {}
|
||||
|
||||
-- get all relevant suggestions for given filename
|
||||
local items = {}
|
||||
for _, v in pairs(map) do
|
||||
if common.match_pattern(filename, v.files) then
|
||||
for _, item in pairs(v.items) do
|
||||
table.insert(items, item)
|
||||
assigned_sym[item.text] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Append the global, local or related text symbols if applicable
|
||||
local scope = config.plugins.autocomplete.suggestions_scope
|
||||
|
||||
if not triggered_manually then
|
||||
local text_symbols = nil
|
||||
|
||||
if scope == "global" then
|
||||
text_symbols = global_symbols
|
||||
elseif scope == "local" and cache[doc] and cache[doc].symbols then
|
||||
text_symbols = cache[doc].symbols
|
||||
elseif scope == "related" then
|
||||
for _, d in ipairs(core.docs) do
|
||||
if doc.syntax == d.syntax then
|
||||
if cache[d].symbols then
|
||||
for name in pairs(cache[d].symbols) do
|
||||
if not assigned_sym[name] then
|
||||
table.insert(items, setmetatable(
|
||||
{text = name, info = "normal"}, mt
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if text_symbols then
|
||||
for name in pairs(text_symbols) do
|
||||
if not assigned_sym[name] then
|
||||
table.insert(items, setmetatable({text = name, info = "normal"}, mt))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -286,13 +394,23 @@ local function get_suggestions_rect(av)
|
|||
y = y + av:get_line_height() + style.padding.y
|
||||
local font = av:get_font()
|
||||
local th = font:get_height()
|
||||
local has_icons = false
|
||||
local hide_info = config.plugins.autocomplete.hide_info
|
||||
local hide_icons = config.plugins.autocomplete.hide_icons
|
||||
|
||||
local max_width = 0
|
||||
for _, s in ipairs(suggestions) do
|
||||
local w = font:get_width(s.text)
|
||||
if s.info then
|
||||
if s.info and not hide_info then
|
||||
w = w + style.font:get_width(s.info) + style.padding.x
|
||||
end
|
||||
local icon = s.icon or s.info
|
||||
if not hide_icons and icon and autocomplete.icons[icon] then
|
||||
w = w + autocomplete.icons[icon].font:get_width(
|
||||
autocomplete.icons[icon].char
|
||||
) + (style.padding.x / 2)
|
||||
has_icons = true
|
||||
end
|
||||
max_width = math.max(max_width, w)
|
||||
end
|
||||
|
||||
|
@ -319,7 +437,8 @@ local function get_suggestions_rect(av)
|
|||
x - style.padding.x,
|
||||
y - style.padding.y,
|
||||
max_width + style.padding.x * 2,
|
||||
max_items * (th + style.padding.y) + style.padding.y
|
||||
max_items * (th + style.padding.y) + style.padding.y,
|
||||
has_icons
|
||||
end
|
||||
|
||||
local function wrap_line(line, max_chars)
|
||||
|
@ -439,7 +558,7 @@ local function draw_suggestions_box(av)
|
|||
local ah = config.plugins.autocomplete.max_height
|
||||
|
||||
-- draw background rect
|
||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||
local rx, ry, rw, rh, has_icons = get_suggestions_rect(av)
|
||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||
|
||||
-- draw text
|
||||
|
@ -448,17 +567,52 @@ local function draw_suggestions_box(av)
|
|||
local y = ry + style.padding.y / 2
|
||||
local show_count = #suggestions <= ah and #suggestions or ah
|
||||
local start_index = suggestions_idx > ah and (suggestions_idx-(ah-1)) or 1
|
||||
local hide_info = config.plugins.autocomplete.hide_info
|
||||
|
||||
for i=start_index, start_index+show_count-1, 1 do
|
||||
if not suggestions[i] then
|
||||
break
|
||||
end
|
||||
local s = suggestions[i]
|
||||
|
||||
local icon_l_padding, icon_r_padding = 0, 0
|
||||
|
||||
if has_icons then
|
||||
local icon = s.icon or s.info
|
||||
if icon and autocomplete.icons[icon] then
|
||||
local ifont = autocomplete.icons[icon].font
|
||||
local itext = autocomplete.icons[icon].char
|
||||
local icolor = autocomplete.icons[icon].color
|
||||
if i == suggestions_idx then
|
||||
icolor = style.accent
|
||||
elseif type(icolor) == "string" then
|
||||
icolor = style.syntax[icolor]
|
||||
end
|
||||
if config.plugins.autocomplete.icon_position == "left" then
|
||||
common.draw_text(
|
||||
ifont, icolor, itext, "left", rx + style.padding.x, y, rw, lh
|
||||
)
|
||||
icon_l_padding = ifont:get_width(itext) + (style.padding.x / 2)
|
||||
else
|
||||
common.draw_text(
|
||||
ifont, icolor, itext, "right", rx, y, rw - style.padding.x, lh
|
||||
)
|
||||
icon_r_padding = ifont:get_width(itext) + (style.padding.x / 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local color = (i == suggestions_idx) and style.accent or style.text
|
||||
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
||||
if s.info then
|
||||
common.draw_text(
|
||||
font, color, s.text, "left",
|
||||
rx + icon_l_padding + style.padding.x, y, rw, lh
|
||||
)
|
||||
if s.info and not hide_info then
|
||||
color = (i == suggestions_idx) and style.text or style.dim
|
||||
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
|
||||
common.draw_text(
|
||||
style.font, color, s.info, "right",
|
||||
rx, y, rw - icon_r_padding - style.padding.x, lh
|
||||
)
|
||||
end
|
||||
y = y + lh
|
||||
if suggestions_idx == i then
|
||||
|
@ -619,6 +773,31 @@ function autocomplete.can_complete()
|
|||
return false
|
||||
end
|
||||
|
||||
---Register a font icon that can be assigned to completion items.
|
||||
---@param name string
|
||||
---@param character string
|
||||
---@param font? renderer.font
|
||||
---@param color? string | renderer.color A style.syntax[] name or specific color
|
||||
function autocomplete.add_icon(name, character, font, color)
|
||||
local color_type = type(color)
|
||||
assert(
|
||||
not color or color_type == "table"
|
||||
or (color_type == "string" and style.syntax[color]),
|
||||
"invalid icon color given"
|
||||
)
|
||||
autocomplete.icons[name] = {
|
||||
char = character,
|
||||
font = font or style.code_font,
|
||||
color = color or "keyword"
|
||||
}
|
||||
end
|
||||
|
||||
--
|
||||
-- Register built-in syntax symbol types icon
|
||||
--
|
||||
for name, _ in pairs(style.syntax) do
|
||||
autocomplete.add_icon(name, "M", style.icon_font, name)
|
||||
end
|
||||
|
||||
--
|
||||
-- Commands
|
||||
|
@ -632,7 +811,6 @@ command.add(predicate, {
|
|||
["autocomplete:complete"] = function(dv)
|
||||
local doc = dv.doc
|
||||
local item = suggestions[suggestions_idx]
|
||||
local text = item.text
|
||||
local inserted = false
|
||||
if item.onselect then
|
||||
inserted = item.onselect(suggestions_idx, item)
|
||||
|
|
Loading…
Reference in New Issue