-- saveplace.lua --- remember the cursor position per file (Arc 3 Q#PS3b). -- -- Records the active file buffer's (cursor byte, view_top) on save and -- at quit, and restores it when the file is reopened. Storage is the -- `places` state file, one ` ` line per file -- (numbers first so the path, which may contain spaces, is the -- whitespace-split remainder). LRU-capped. -- -- On by default; disable from init.lua with -- `pmacs.saveplace.enable(false)`. Inert when no state dir is -- configured (cfg(test) / no HOME), so the lib suite writes nothing. -- -- Framing: docs/persistence-framing.md. pmacs.saveplace = pmacs.saveplace or {} local STATE_KEY = "places" local MAX_ENTRIES = 200 local enabled = true function pmacs.saveplace.enable(on) enabled = (on ~= false) end local function active_ready() return enabled and pmacs.state.available() and pmacs.editor.file_path() ~= nil end -- Load the places file into an ordered list of {path, cursor, view_top} -- (most-recently-recorded first) plus a path->index lookup. local function load_places() local list, index = {}, {} local text = pmacs.state.read(STATE_KEY) if not text then return list, index end for line in text:gmatch("([^\n]+)") do -- " " local cur, vt, path = line:match("^(%d+)%s+(%d+)%s+(.+)$") if path and not index[path] then list[#list + 1] = { path = path, cursor = tonumber(cur), view_top = tonumber(vt) } index[path] = #list end end return list, index end local function save_places(list) local lines = {} for i = 1, math.min(#list, MAX_ENTRIES) do local e = list[i] lines[#lines + 1] = string.format("%d %d %s", e.cursor, e.view_top, e.path) end pmacs.state.write(STATE_KEY, table.concat(lines, "\n") .. (#lines > 0 and "\n" or "")) end -- Record the active buffer's place, moving it to the front (LRU). local function record_active() if not active_ready() then return end local path = pmacs.editor.file_path() local cursor = pmacs.editor.cursor() local view_top = pmacs.editor.view_top() local list, index = load_places() if index[path] then table.remove(list, index[path]) end table.insert(list, 1, { path = path, cursor = cursor, view_top = view_top }) save_places(list) end -- Restore the just-loaded file's place, if we have one. local function restore_active() if not active_ready() then return end local path = pmacs.editor.file_path() local list, index = load_places() local i = index[path] if not i then return end local e = list[i] pmacs.editor.goto_byte(e.cursor) pmacs.editor.set_view_top(e.view_top) end -- Record on save and on quit; restore on open. before-save / -- before-quit are short-circuit hooks — returning nil never vetoes. pmacs.hook.add("buffer.before-save", function() pcall(record_active) end) pmacs.hook.add("editor.before-quit", function() pcall(record_active) end) pmacs.hook.add("buffer.after-load", function() pcall(restore_active) end)