Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions cmd/micro/initlua.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"log"
"time"

"github.com/micro-editor/tcell/v2"
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"

Expand All @@ -12,6 +13,7 @@ import (
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/overlay"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
Expand All @@ -35,6 +37,8 @@ func LuaImport(pkg string) *lua.LTable {
return luaImportMicroConfig()
case "micro/util":
return luaImportMicroUtil()
case "micro/overlay":
return luaImportMicroOverlay()
default:
return ulua.Import(pkg)
}
Expand Down Expand Up @@ -163,3 +167,20 @@ func luaImportMicroUtil() *lua.LTable {

return pkg
}

func luaImportMicroOverlay() *lua.LTable {
pkg := ulua.L.NewTable()

ulua.L.SetField(pkg, "CreateOverlay", luar.New(ulua.L, overlay.CreateOverlay))
ulua.L.SetField(pkg, "DestroyOverlay", luar.New(ulua.L, overlay.DestroyOverlay))
ulua.L.SetField(pkg, "DrawText", luar.New(ulua.L, overlay.DrawText))
ulua.L.SetField(pkg, "DrawRect", luar.New(ulua.L, overlay.DrawRect))
ulua.L.SetField(pkg, "BufPaneScreenRect", luar.New(ulua.L, overlay.BufPaneScreenRect))
ulua.L.SetField(pkg, "BufPaneScreenLoc", luar.New(ulua.L, overlay.BufPaneScreenLoc))
ulua.L.SetField(pkg, "Style", luar.New(ulua.L, func() tcell.Style { return tcell.Style{} }))
ulua.L.SetField(pkg, "GetColor", luar.New(ulua.L, config.GetColor))
ulua.L.SetField(pkg, "StringToStyle", luar.New(ulua.L, config.StringToStyle))
ulua.L.SetField(pkg, "Redraw", luar.New(ulua.L, screen.Redraw))

return pkg
}
2 changes: 2 additions & 0 deletions cmd/micro/micro.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/overlay"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
Expand Down Expand Up @@ -469,6 +470,7 @@ func DoEvent() {
}
action.MainTab().Display()
action.InfoBar.Display()
overlay.DisplayOverlays()
screen.Screen.Show()

// Check for new events
Expand Down
4 changes: 4 additions & 0 deletions internal/display/bufwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ func (w *BufWindow) BufView() View {
}
}

func (w *BufWindow) GutterOffset() int {
return w.gutterOffset
}

func (w *BufWindow) updateDisplayInfo() {
b := w.Buf

Expand Down
1 change: 1 addition & 0 deletions internal/lua/lua.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ func importStrings() *lua.LTable {
L.SetField(pkg, "ContainsAny", luar.New(L, strings.ContainsAny))
L.SetField(pkg, "ContainsRune", luar.New(L, strings.ContainsRune))
L.SetField(pkg, "Count", luar.New(L, strings.Count))
L.SetField(pkg, "Cut", luar.New(L, strings.Cut))
L.SetField(pkg, "EqualFold", luar.New(L, strings.EqualFold))
L.SetField(pkg, "Fields", luar.New(L, strings.Fields))
L.SetField(pkg, "FieldsFunc", luar.New(L, strings.FieldsFunc))
Expand Down
130 changes: 130 additions & 0 deletions internal/overlay/overlay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package overlay

import (
"github.com/mattn/go-runewidth"
"github.com/micro-editor/tcell/v2"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
)

type OverlayHandle int
type OverlayFunction func()

type Rect struct {
X, Y, W, H int
}

var overlay_handle = OverlayHandle(0)
var overlays = make(map[OverlayHandle]OverlayFunction)

func DisplayOverlays() {
// Should an OverlayFunction create or destroy an overlay, that would modify
// the overlays map while we are iterating through it.
// For this reason, we copy the overlays map into temp_overlays.

temp_overlays := make(map[OverlayHandle]OverlayFunction, len(overlays))

for h, o := range overlays {
temp_overlays[h] = o
}

for _, draw_fn := range temp_overlays {
draw_fn()
}
}

// CreateOverlay creates and registers a new overlay, and returns
// the OverlayHandle associated with it.
func CreateOverlay(draw OverlayFunction) OverlayHandle {
overlay_handle++
overlays[overlay_handle] = draw
return overlay_handle
}

// DestroyOverlay destroys/deregisters an existing overlay via its handle.
func DestroyOverlay(overlay OverlayHandle) {
delete(overlays, overlay)
}

// DrawRect draws a flat styled rectangle to the provided screen coordinates.
func DrawRect(x, y, w, h int, style tcell.Style) {
for yy := 0; yy < h; yy++ {
for xx := 0; xx < w; xx++ {
screen.SetContent(x+xx, y+yy, ' ', nil, style)
}
}
}

// DrawText draws styled clipped text to the provided screen coordinates.
func DrawText(text string, x, y, w, h int, style tcell.Style) {
DrawRect(x, y, w, h, style)

tabsize := util.IntOpt(config.GlobalSettings["tabsize"])
text_bytes := []byte(text)
xx := 0
yy := 0

for len(text_bytes) > 0 {
r, combc, size := util.DecodeCharacter(text_bytes)
text_bytes = text_bytes[size:]
width := 0

switch r {
case '\t':
width = tabsize - (xx % tabsize)
case '\n':
xx = 0
yy++
continue
default:
width = runewidth.RuneWidth(r)
}

if yy > h {
break
}

if xx+width <= w {
screen.SetContent(x+xx, y+yy, r, combc, style)
}

xx += width
}
}

// BufPaneScreenRect returns the bounds of a BufPane in screen coordinates.
func BufPaneScreenRect(bp *action.BufPane) Rect {
// NOTE: This function is a very thin wrapper around bp.GetView(). As such,
// it is maybe a candidate for removal?
v := bp.GetView()
return Rect{
X: v.X,
Y: v.Y,
W: v.Width,
H: v.Height,
}
}

// BufPaneScreenLoc converts a Loc in the buffer displayed in
// a bufpane to screen coordinates.
func BufPaneScreenLoc(bp *action.BufPane, loc buffer.Loc) buffer.Loc {
gutter := 0
bw, ok := bp.BWindow.(*display.BufWindow)
if ok {
gutter = bw.GutterOffset()
}

v := bp.GetView()
vloc := bp.VLocFromLoc(loc)
top := v.StartLine
yoff := bp.Diff(top, vloc.SLoc)

return buffer.Loc{
X: v.X + gutter + vloc.VisualX,
Y: v.Y + yoff,
}
}
26 changes: 26 additions & 0 deletions runtime/help/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,32 @@ The packages and their contents are listed below (in Go type signatures):
Relevant links:
[Rune](https://pkg.go.dev/builtin#rune)

* `micro/overlay`
- `CreateOverlay(draw func()) OverlayHandle`: creates and registers a new
overlay, and returns the OverlayHandle associated with it.
- `DestroyOverlay(handle OverlayHandle)`: deregisters an existing overlay
via its handle.
- `DrawText(text string, x, y, w, h int, style tcell.Style)`: draws styled
text clipped to the bounds of the provided screen rectangle.
- `DrawRect(x, y, w, h int, style tcell.Style)`: draws a rectangle to the
provided screen coordinates.
- `BufPaneScreenRect(bp BufPane) overlay.Rect`: returns the bounds of a
BufPane in screen coordinates.
- `BufPaneScreenLoc(bp BufPane, l Loc) Loc`: converts from line/column
coordinates to screen coordinates.
- `Style() tcell.Style`: returns a default (empty) tcell.Style.
- `GetColor(name string) tcell.Style`: takes in a syntax group and returns
the colorscheme's style for that group.
- `StringToStyle(str string) tcell.Style`: returns a style from a string.
The string must be in the format "extra foregroundcolor,backgroundcolor".
The "extra" can be bold, reverse, italic or underline.
- `Redraw()`: schedules a redraw of the entire screen.

Relevant links:
[BufPane](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#BufPane)
[tcell.Style](https://pkg.go.dev/github.com/micro-editor/tcell/v2#Style)


This may seem like a small list of available functions, but some of the objects
returned by the functions have many methods. The Lua plugin may access any
public methods of an object returned by any of the functions above.
Expand Down
144 changes: 144 additions & 0 deletions runtime/plugins/completebox/completebox.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
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
5 changes: 5 additions & 0 deletions runtime/plugins/completebox/help/completebox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# CompleteBox Plugin

The completebox plugin demonstrates a simple way to hook
into micro's autocomplete mechanism to display the list of
available completions as an overlay at the cursor.
Loading