mirror of
https://github.com/zyedidia/micro.git
synced 2025-06-18 23:05:40 -04:00
370 lines
8.4 KiB
Lua
370 lines
8.4 KiB
Lua
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
|