362 lines
7.1 KiB
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
|