notes/.config/micro/plug/bookmark/bookmark.lua

362 lines
7.1 KiB
Lua

VERSION = "2.2.0"
local micro = import("micro")
local buffer = import("micro/buffer")
local config = import("micro/config")
-- buffer count
local bc = 0
-- buffer bookmark data
local bd = {}
-- mark/unmark current line
function _toggle(bp)
local bn = bp.Buf:GetName()
local c = bp.Buf:GetActiveCursor()
local newy = c.Loc.Y
local oldy = false
-- remove mark if already present
for i,y in ipairs(bd[bn].marks) do
if y == newy then
oldy = true
table.remove(bd[bn].marks, i)
break
end
end
-- add mark if not already present
if oldy == false then
table.insert(bd[bn].marks, newy)
end
-- if there are marks, sort
if #bd[bn].marks > 0 then
table.sort(bd[bn].marks)
end
_redraw(bp)
end
-- clear all bookmarks
function _clear(bp)
local bn = bp.Buf:GetName()
bd[bn].marks = {}
collectgarbage()
bp.Buf:ClearMessages("bookmark")
_gutter(bp, #bd[bn].marks.."", 0)
end
-- jump to next bookmark
function _next(bp)
local bn = bp.Buf:GetName()
-- no action if no marks
if #bd[bn].marks == 0 then return; end
local c = bp.Buf:GetActiveCursor()
-- look to see if there are any marks lower in the buffer
local noneBelow = true
for i,y in ipairs(bd[bn].marks) do
if y > c.Loc.Y then
c.Loc.Y = y
noneBelow = false
break
end
end
-- if there's nothing lower, go to the first (highest) mark
if noneBelow == true then
c.Loc.Y = bd[bn].marks[1]
end
bp:Relocate()
end
-- jump to previous bookmark
function _prev(bp)
local bn = bp.Buf:GetName()
-- no action if no marks
if #bd[bn].marks == 0 then return; end
local c = bp.Buf:GetActiveCursor()
-- look to see if there are any marks higher in the buffer
local noneAbove = true
local i = #bd[bn].marks
while (true) do
local y = bd[bn].marks[i]
if y < c.Loc.Y then
c.Loc.Y = y
noneAbove = false
i = 1
end
i = i - 1
if i == 0 then break; end
end
-- if there's nothing higher, go to the last (lowest) mark
if noneAbove == true then
c.Loc.Y = bd[bn].marks[#bd[bn].marks]
end
bp:Relocate()
end
-- handlers
function preDuplicateLine(bp)
_save_pre_state(bp)
end
function onDuplicateLine(bp)
_update(bp)
end
function preInsertNewline(bp)
_save_pre_state(bp)
end
function onInsertNewline(bp)
local bn = bp.Buf:GetName()
-- if cursor is not at start of bookmarked line enter is pressed, don't move the bookmark
if bd[bn].curpos.X ~= 0 and bd[bn].onmark then
bd[bn].oldl = bp.Buf:LinesNum()
else
_update(bp)
end
end
function preDelete(bp)
_save_pre_state(bp)
end
function onDelete(bp)
_update(bp)
end
function preCut(bp)
_save_pre_state(bp)
end
function onCut(bp)
_update(bp)
end
function preCutLine(bp)
_save_pre_state(bp)
end
function onCutLine(bp)
_update(bp)
end
function preBackspace(bp)
_save_pre_state(bp)
end
function onBackspace(bp)
_update(bp)
end
function preUndo(bp)
_save_pre_state(bp)
end
function onUndo(bp)
_update(bp)
end
function preRedo(bp)
_save_pre_state(bp)
end
function onRedo(bp)
_update(bp)
end
-- update bookmark positions
function _update(bp)
local bn = bp.Buf:GetName()
local newl = bp.Buf:LinesNum()
local diff = math.abs(newl - bd[bn].oldl)
-- only update if lines have been added or removed
if diff then
if newl < bd[bn].oldl then
diff = -diff
end
bd[bn].oldl = newl
local c = bp.Buf:GetActiveCursor()
-- add or subtract lines for all marks below current line
for i,y in ipairs(bd[bn].marks) do
-- update bookmarks above cursor line when lines have been removed
-- or update bookmarks at or below cursor line when lines have been added
if diff > 0 and y >= bd[bn].curpos.Y or diff < 0 and y > c.Loc.Y then
-- move bookmarks in text selection to first line of selection
if bd[bn].sel[1].Y < y and bd[bn].sel[2].Y > y then
bd[bn].marks[i] = bd[bn].sel[1].Y
else
-- otherwise just add the difference
bd[bn].marks[i] = y + diff
end
end
end
-- after bookmarks have been moved, remove any overlapping ones
_dedupe(bp)
-- then redraw the lot
_redraw(bp)
end
end
-- remove duplicate using hash table
function _dedupe(bp)
local bn = bp.Buf:GetName()
local hash = {}
local res = {}
for k,v in ipairs(bd[bn].marks) do
if not hash[v] then
res[#res+1] = v
hash[v] = true
end
end
bd[bn].marks = res
end
-- clear gutter and redraw all marks
function _redraw(bp)
local bn = bp.Buf:GetName()
bp.Buf:ClearMessages("bookmark")
if #bd[bn].marks > 0 then
for i,y in ipairs(bd[bn].marks) do
_gutter(bp, "bookmark ("..i.."/"..#bd[bn].marks..")", y + 1)
end
else
-- prevent text shift caused by gutter open/close action by keeping it open
_gutter(bp, "", 0)
end
end
-- print gutter message
function _gutter(bp, msg, line)
bp.Buf:AddMessage(buffer.NewMessageAtLine("bookmark", msg, line, buffer.MTInfo))
end
-- track cursor position, selected lines and whether current line is marked or not
-- this information will be used in the onAction handlers for bookmark positioning
function _save_pre_state(bp)
local bn = bp.Buf:GetName()
-- save cursor position
bd[bn].curpos = -bp.Cursor.Loc
-- save mark state of current line
bd[bn].onmark = false
for i,y in ipairs(bd[bn].marks) do
if y == bd[bn].curpos.Y then
bd[bn].onmark = true
break
end
end
-- save text selection range
if bp.Cursor:HasSelection() then
bd[bn].sel = -bp.Cursor.CurSelection
else
bd[bn].sel = {
{ Y = bd[bn].curpos.Y },
{ Y = bd[bn].curpos.Y }
}
end
end
-- called whenever new buffer is opened
function onBufferOpen(b)
local bn = b:GetName()
-- skip system buffers
-- if bn == "No name" or bn == "Log" then return; end
-- keep count of opened buffers
bc = bc + 1
-- init data table
bd[bn] = {
-- table of bookmarks, where each bookmark is a line number
marks = {},
-- track cursor position
curpos = {},
-- track selected text
sel = {},
-- track bookmark state of current line
onmark = false,
-- track buffer lines
oldl = 0
}
end
-- called when a buffer pane is ready
function onBufPaneOpen(bp)
local bn = bp.Buf:GetName()
-- init vars
bd[bn].oldl = bp.Buf:LinesNum()
_save_pre_state(bp)
_redraw(bp)
--~ printBufferNames()
end
-- called when buffer is closed
function onQuit(bp)
-- decrement buffer count
bc = bc - 1
-- clear buffer bookmark data
bd[bp.Buf:GetName()] = nil
--~ printBufferNames()
end
--~ function printBufferNames()
--~ local bufstr = ""
--~ for bn,d in pairs(bd) do
--~ bufstr = bufstr..(bufstr == "" and "" or " | ")..bn
--~ end
--~ micro.InfoBar():Message(bufstr)
--~ end
function init()
-- setup our commands for autocomplete
config.MakeCommand("toggleBookmark", _toggle, config.OptionComplete)
config.MakeCommand("nextBookmark", _next, config.OptionComplete)
config.MakeCommand("prevBookmark", _prev, config.OptionComplete)
config.MakeCommand("clearBookmarks", _clear, config.OptionComplete)
-- setup default bindings
config.TryBindKey("Ctrl-F2", "command:toggleBookmark", false)
config.TryBindKey("CtrlShift-F2", "command:clearBookmarks", false)
config.TryBindKey("F2", "command:nextBookmark", false)
config.TryBindKey("Shift-F2", "command:prevBookmark", false)
-- add our help topic
config.AddRuntimeFile("bookmark", config.RTHelp, "help/bookmark.md")
end