mirror of
https://github.com/zyedidia/micro.git
synced 2025-06-18 14:55:38 -04:00
Overlays, example overlay plugins
This commit is contained in:
parent
06fe85c8c9
commit
051d715457
@ -4,6 +4,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
@ -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"
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
@ -469,6 +470,7 @@ func DoEvent() {
|
||||
}
|
||||
action.MainTab().Display()
|
||||
action.InfoBar.Display()
|
||||
overlay.DisplayOverlays()
|
||||
screen.Screen.Show()
|
||||
|
||||
// Check for new events
|
||||
|
@ -109,6 +109,10 @@ func (w *BufWindow) BufView() View {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BufWindow) GutterOffset() int {
|
||||
return w.gutterOffset
|
||||
}
|
||||
|
||||
func (w *BufWindow) updateDisplayInfo() {
|
||||
b := w.Buf
|
||||
|
||||
|
@ -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))
|
||||
|
130
internal/overlay/overlay.go
Normal file
130
internal/overlay/overlay.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
144
runtime/plugins/completebox/completebox.lua
Normal file
144
runtime/plugins/completebox/completebox.lua
Normal file
@ -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
runtime/plugins/completebox/help/completebox.md
Normal file
5
runtime/plugins/completebox/help/completebox.md
Normal file
@ -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.
|
21
runtime/plugins/quickmenu/help/quickmenu.md
Normal file
21
runtime/plugins/quickmenu/help/quickmenu.md
Normal file
@ -0,0 +1,21 @@
|
||||
# QuickMenu Plugin
|
||||
|
||||
The quickmenu plugin is a slightly more involved example of what micro's new
|
||||
overlay system can do.
|
||||
|
||||
The plugin exposes a palette-like quickmenu that can be used to quickly find
|
||||
files by name (via 'find') or by content (via 'grep').
|
||||
|
||||
It exposes two new commands, and a single global option.
|
||||
|
||||
Commands:
|
||||
* `quicksearch`: Opens the find-by-name menu.
|
||||
* `quickopen`: Opens the find-by-contents menu.
|
||||
|
||||
By default, quicksearch will be bound to `Alt-f`, and quickopen to `Alt-o`
|
||||
|
||||
Options:
|
||||
* `quickmenu.newtab`: when a file is opened via the quickmenu, it will be opened
|
||||
in a new tab.
|
||||
|
||||
default value: `true`
|
369
runtime/plugins/quickmenu/quickmenu.lua
Normal file
369
runtime/plugins/quickmenu/quickmenu.lua
Normal file
@ -0,0 +1,369 @@
|
||||
local micro = import("micro")
|
||||
local config = import("micro/config")
|
||||
local buffer = import("micro/buffer")
|
||||
local overlay = import("micro/overlay")
|
||||
local shell = import("micro/shell")
|
||||
local strings = import("strings")
|
||||
local pathlib = import("path")
|
||||
|
||||
local last_job = nil
|
||||
local results = {}
|
||||
|
||||
function wrap_int(val, min, max)
|
||||
if min==max then return min end
|
||||
local range = max - min + 1
|
||||
return min + (val - min) % range
|
||||
end
|
||||
|
||||
function clamp(val, min, max)
|
||||
if val < min then return min end
|
||||
if val > max then return max end
|
||||
return val
|
||||
end
|
||||
|
||||
function array_get(arr, idx)
|
||||
if idx <= #arr then
|
||||
return arr[idx]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function cancel_job(job)
|
||||
if job and not job.ProcessState then
|
||||
shell.JobStop(job)
|
||||
end
|
||||
end
|
||||
|
||||
function find(query)
|
||||
cancel_job(last_job)
|
||||
local job = nil
|
||||
results = {}
|
||||
|
||||
local parts = strings.Fields(query)
|
||||
local args = {".", "-type", "f"}
|
||||
|
||||
for i, part in parts() do
|
||||
if i>1 then
|
||||
args[#args+1] = "-and"
|
||||
end
|
||||
args[#args+1] = "-ipath"
|
||||
args[#args+1] = "*"..part.."*"
|
||||
end
|
||||
|
||||
function on_stdout(data)
|
||||
if job~=last_job then
|
||||
cancel_job(job)
|
||||
return
|
||||
end
|
||||
|
||||
local new_results = strings.Split(data, "\n")
|
||||
for _, path in new_results() do
|
||||
if #path>0 then
|
||||
results[#results+1] = {type="file",path=path}
|
||||
end
|
||||
end
|
||||
|
||||
overlay.Redraw()
|
||||
if #results>20 then
|
||||
cancel_job()
|
||||
end
|
||||
end
|
||||
|
||||
function on_stderr()
|
||||
cancel_job(job)
|
||||
end
|
||||
|
||||
job = shell.JobSpawn(
|
||||
"find", args, on_stdout, on_stderr, nil
|
||||
)
|
||||
last_job = job
|
||||
end
|
||||
|
||||
function grep(query)
|
||||
cancel_job(last_job)
|
||||
local job = nil
|
||||
results = {}
|
||||
|
||||
function on_stdout(data)
|
||||
if job~=last_job then
|
||||
cancel_job(job)
|
||||
return
|
||||
end
|
||||
|
||||
local new_results = strings.Split(data, "\n")
|
||||
for _, res in new_results() do
|
||||
local path, line, content, ok
|
||||
|
||||
path, res, ok = strings.Cut(res, ":")
|
||||
if ok then
|
||||
line, content, ok = strings.Cut(res, ":")
|
||||
|
||||
if ok then
|
||||
results[#results+1] = {
|
||||
type="line",
|
||||
path=path,
|
||||
line=line,
|
||||
content=content
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
overlay.Redraw()
|
||||
if #results>10 then
|
||||
cancel_job(job)
|
||||
end
|
||||
end
|
||||
|
||||
function on_stderr()
|
||||
cancel_job(job)
|
||||
end
|
||||
|
||||
job = shell.JobSpawn(
|
||||
"grep", {"-rn", query, "."},
|
||||
on_stdout, on_stderr, nil
|
||||
)
|
||||
last_job = job
|
||||
end
|
||||
|
||||
-- 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] = {...}
|
||||
event_count = event_count + 1
|
||||
return false
|
||||
end
|
||||
end
|
||||
else
|
||||
_G[full_name] = function(...)
|
||||
if overlay_handle then
|
||||
events[name] = {...}
|
||||
event_count = event_count + 1
|
||||
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 dispatch(event_name, ...)
|
||||
-- Lets us dispatch our own custom events
|
||||
local pre_event = _G["pre"..event_name]
|
||||
local on_event = _G["on"..event_name]
|
||||
|
||||
if pre_event then
|
||||
local res = pre_event(...)
|
||||
if not res then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if on_event then
|
||||
on_event(...)
|
||||
end
|
||||
end
|
||||
|
||||
function event(event_name, block)
|
||||
-- Returns event arguments if the event has occurred, or nil otherwise.
|
||||
track_event(event_name, block)
|
||||
return events[event_name]
|
||||
end
|
||||
|
||||
function close_finder()
|
||||
-- Closes the overlay and untracks all events.
|
||||
untrack_events()
|
||||
overlay.DestroyOverlay(overlay_handle)
|
||||
overlay_handle = nil
|
||||
end
|
||||
|
||||
local mode = "quicksearch"
|
||||
local query = ""
|
||||
local current_result = 1
|
||||
|
||||
function rerun_query(query)
|
||||
if mode == "quicksearch" then
|
||||
grep(query)
|
||||
elseif mode == "quickopen" then
|
||||
find(query)
|
||||
end
|
||||
end
|
||||
|
||||
function preRune(_, r)
|
||||
-- Note: We handle rune events like this because we could
|
||||
-- get more than one rune event per render (for example,
|
||||
-- if the redraw is slow for whatever reason and the
|
||||
-- user is typing fast).
|
||||
if overlay_handle then
|
||||
query = query .. r
|
||||
current_result = 1
|
||||
rerun_query(query)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function draw_finder()
|
||||
local bp = micro.CurPane()
|
||||
|
||||
if event("Escape") then
|
||||
close_finder()
|
||||
return
|
||||
end
|
||||
|
||||
if event("Backspace") then
|
||||
query = query:sub(1, -2)
|
||||
current_result = 1
|
||||
rerun_query(query)
|
||||
end
|
||||
|
||||
if event("InsertNewline") then
|
||||
local result = results[current_result]
|
||||
|
||||
if result then
|
||||
local buf_path = pathlib.Clean(bp.Buf.Path)
|
||||
result.path = pathlib.Clean(result.path)
|
||||
|
||||
if config.GetGlobalOption("quickmenu.newtab") and buf_path~=result.path then
|
||||
bp:NewTabCmd{result.path}
|
||||
bp = micro.CurPane()
|
||||
else
|
||||
bp:OpenCmd{result.path}
|
||||
end
|
||||
|
||||
if result.type == "line" then
|
||||
bp:GotoLoc{X=0, Y=tonumber(result.line)-1}
|
||||
end
|
||||
end
|
||||
|
||||
close_finder()
|
||||
return
|
||||
end
|
||||
|
||||
-- TODO: Make the Left and Right arrow keys work too!
|
||||
if event("CursorUp") then current_result = current_result-1 end
|
||||
if event("CursorDown") then current_result = current_result+1 end
|
||||
local result_count = clamp(#results, 1, 10)
|
||||
current_result = wrap_int(current_result, 1, result_count+1)
|
||||
|
||||
local r = overlay.BufPaneScreenRect(bp)
|
||||
|
||||
local x = math.floor(r.X + r.W*0.15)
|
||||
local w = math.ceil(r.W*0.7)
|
||||
local y = r.Y + 2
|
||||
|
||||
-- Draw the input box
|
||||
local input_style = overlay.GetColor("line-number")
|
||||
|
||||
overlay.DrawRect(x-1, y, w+2, 1, input_style)
|
||||
overlay.DrawText(query, x, y, w, 1, input_style)
|
||||
|
||||
if query=="" then
|
||||
if mode=="quicksearch" then
|
||||
overlay.DrawText("Search code...", x, y, w, 1, input_style:Dim(true))
|
||||
elseif mode=="quickopen" then
|
||||
overlay.DrawText("Find file...", x, y, w, 1, input_style:Dim(true))
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw the results
|
||||
local normal = overlay.GetColor("line-number")
|
||||
local highlight = overlay.GetColor("selection")
|
||||
|
||||
for i, result in pairs(results) do
|
||||
local style = normal
|
||||
if i==current_result then
|
||||
style = highlight
|
||||
end
|
||||
|
||||
if result.type=="line" then
|
||||
y = y+1
|
||||
overlay.DrawText(result.path..":"..result.line, x-1, y, w+2, 1, style:Bold(true))
|
||||
|
||||
y = y+1
|
||||
overlay.DrawRect(x-1, y, w+2, 1, style)
|
||||
overlay.DrawText(" " .. result.content, x, y, w, 1, style)
|
||||
|
||||
elseif result.type=="file" then
|
||||
y = y+1
|
||||
overlay.DrawText(result.path, x, y, w, 1, style:Bold(true))
|
||||
|
||||
end
|
||||
|
||||
if i>10 then break end
|
||||
end
|
||||
|
||||
reset_events()
|
||||
end
|
||||
|
||||
function open_finder(q)
|
||||
if overlay_handle then return end
|
||||
reset_events()
|
||||
|
||||
if q then
|
||||
query = q
|
||||
else
|
||||
query = ""
|
||||
end
|
||||
|
||||
results = {}
|
||||
current_result = 1
|
||||
overlay_handle = overlay.CreateOverlay(draw_finder)
|
||||
end
|
||||
|
||||
function open_quickopen(_, args)
|
||||
mode = "quickopen"
|
||||
open_finder(array_get(args, 1))
|
||||
end
|
||||
|
||||
function open_quicksearch(_, args)
|
||||
mode = "quicksearch"
|
||||
open_finder(array_get(args, 1))
|
||||
end
|
||||
|
||||
function init()
|
||||
config.AddRuntimeFile("quickmenu", config.RTHelp, "help/quickmenu.md")
|
||||
|
||||
config.RegisterGlobalOption("quickmenu", "newtab", true)
|
||||
config.MakeCommand("quicksearch", open_quicksearch, config.NoComplete)
|
||||
config.MakeCommand("quickopen", open_quickopen, config.NoComplete)
|
||||
config.TryBindKey("Alt-f", "command:quicksearch", false)
|
||||
config.TryBindKey("Alt-o", "command:quickopen", false)
|
||||
end
|
||||
|
||||
function deinit()
|
||||
close_finder()
|
||||
untrack_events()
|
||||
end
|
Loading…
Reference in New Issue
Block a user