micro/runtime/plugins/completebox/completebox.lua
2025-05-03 12:50:36 +02:00

145 lines
3.2 KiB
Lua

VERSION = "1.0.0"
local micro = import("micro")
local config = import("micro/config")
local buffer = import("micro/buffer")
local overlay = import("micro/overlay")
-- Immediate-mode event handling
local overlay_handle = nil
local event_count = 0
local events = {}
local tracked_events = {}
function track_event(name, block)
-- Registers a global handler for an event
-- If "no_block" is passed as the second argument,
-- the event will not be prevented.
local full_name = "pre" .. name
if block=="no_block" then
full_name = "on"..name
end
if not tracked_events[full_name] then
tracked_events[full_name] = true
if block~="no_block" then
_G[full_name] = function()
if overlay_handle then
events[name] = true
event_count = event_count + 1
end
end
else
_G[full_name] = function()
if overlay_handle then
events[name] = true
event_count = event_count + 1
return false
end
end
end
end
end
function untrack_events()
-- Removes all global event handlers
for e, _ in pairs(tracked_events) do
_G[e] = nil
end
tracked_events = {}
end
function reset_events()
-- Resets tracked events between redraws
events = {}
event_count = 0
end
function event(event_name, block)
-- Returns true if the event has occured.
track_event(event_name, block)
return events[event_name] or false
end
function close_overlay()
-- Closes the overlay and untracks all events.
untrack_events()
overlay.DestroyOverlay(overlay_handle)
overlay_handle = nil
end
function max_len(iter)
-- Returns the length of the longest string in iterable
local max = 0
for _, item in iter do
max = math.max(max, #item)
end
return max
end
function draw_autocomplete_overlay()
local bp = micro.CurPane()
local buf = bp.Buf
if not buf.HasSuggestions then
-- If there are no suggestions, we close the overlay.
close_overlay()
return
end
-- These events should not close the menu, so we track them, but
-- we do not block them, because we want autocomplete cycling to work.
event("CycleAutocomplete", "no_block")
event("CycleAutocompleteBack", "no_block")
-- Positioning adjustment - show the menu below where the cursor
-- was when autocomplete was initiated by subtracting the length
-- of the currently applied completion.
local compl_len = #buf.Completions[buf.CurSuggestion+1] + 1
-- Note: The minus dereferences the Loc pointer
local l = -buf:GetActiveCursor().Loc
l = overlay.BufPaneScreenLoc(bp, l)
local x = l.X-compl_len
local y = l.Y+1
-- Calculate the maximum text width of the options,
-- add 2 cells of padding
local w = max_len(buf.Suggestions())+2
-- Draw each option, highlight the current option
local yoff = 0
local style = overlay.GetColor("cursor-line")
for i, option in buf.Suggestions() do
local style = overlay.Style()
if i == buf.CurSuggestion+1 then
style = overlay.GetColor("statusline")
end
overlay.DrawText(" "..option, x, y+yoff, w, 1, style)
yoff = yoff+1
end
reset_events()
end
function init()
config.AddRuntimeFile("completebox", config.RTHelp, "help/completebox.md")
end
function deinit()
close_overlay()
untrack_events()
end
function onAutocomplete()
if overlay_handle then return end
reset_events()
overlay_handle = overlay.CreateOverlay(draw_autocomplete_overlay)
end