-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinit.lua
More file actions
338 lines (301 loc) · 8.96 KB
/
Copy pathinit.lua
File metadata and controls
338 lines (301 loc) · 8.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
local config = require("peekstack.config")
local stack = require("peekstack.core.stack")
local location = require("peekstack.core.location")
local store = require("peekstack.persist.store")
local migrate = require("peekstack.persist.migrate")
local user_events = require("peekstack.core.user_events")
local M = {}
local SCOPE = "repo"
---@type table<string, PeekstackSession>
local cached_sessions = {}
local cache_loaded = false
---@param data PeekstackStoreData
---@return PeekstackStoreData
local function update_cache(data)
local ensured = migrate.ensure(data)
cached_sessions = ensured.sessions or {}
cache_loaded = true
return ensured
end
---Check if persistence is enabled, notify if not
---@param silent? boolean
---@return boolean
local function ensure_enabled(silent)
if not config.get().persist.enabled then
if not silent then
vim.notify("peekstack.persist is disabled", vim.log.levels.INFO)
end
return false
end
return true
end
---@param name? string
---@return string
local function resolve_name(name)
if name and name ~= "" then
return name
end
local cfg = config.get()
if cfg.persist.session and cfg.persist.session.default_name then
return cfg.persist.session.default_name
end
return "default"
end
---@param root_winid? integer
---@return integer
local function resolve_root_winid(root_winid)
if root_winid and type(root_winid) == "number" and vim.api.nvim_win_is_valid(root_winid) then
return root_winid
end
local winid = vim.api.nvim_get_current_win()
local bufnr = vim.api.nvim_win_get_buf(winid)
if vim.bo[bufnr].filetype == "peekstack-stack" then
local ok, stack_root_winid = pcall(vim.api.nvim_win_get_var, winid, "peekstack_root_winid")
if ok and type(stack_root_winid) == "number" and vim.api.nvim_win_is_valid(stack_root_winid) then
return stack_root_winid
end
end
return winid
end
---Save the current stack to persistent storage with optional name
---@param name? string
---@param opts? { scope?: string, root_winid?: integer, silent?: boolean, sync?: boolean, on_done?: fun(success: boolean) }
function M.save_current(name, opts)
local silent = opts and opts.silent or false
local sync = opts and opts.sync or false
local on_done = opts and opts.on_done or nil
local function finish(success)
if on_done then
on_done(success)
end
end
if not ensure_enabled(silent) then
finish(false)
return
end
local resolved_name = resolve_name(name)
local scope = SCOPE
local items = stack.list(resolve_root_winid(opts and opts.root_winid or nil))
local data_items = {}
for _, popup in ipairs(items) do
table.insert(data_items, {
uri = popup.location.uri,
range = popup.location.range,
title = popup.title,
provider = popup.location.provider,
ts = os.time(),
})
end
local max_items = config.get().persist.max_items or 200
if #data_items > max_items then
data_items = vim.list_slice(data_items, #data_items - max_items + 1, #data_items)
end
---@param data PeekstackStoreData
---@return PeekstackStoreData
local function upsert_session(data)
local now = os.time()
if data.sessions[resolved_name] then
data.sessions[resolved_name].items = data_items
data.sessions[resolved_name].meta.updated_at = now
else
data.sessions[resolved_name] = {
items = data_items,
meta = {
created_at = now,
updated_at = now,
},
}
end
return data
end
local function notify_save_result(success)
if not silent then
if success then
vim.notify("Session saved: " .. resolved_name, vim.log.levels.INFO)
else
vim.notify("Failed to save session: " .. resolved_name, vim.log.levels.WARN)
end
end
if success then
user_events.emit("PeekstackSave", {
session = resolved_name,
item_count = #data_items,
})
end
end
if sync then
local data = upsert_session(migrate.ensure(store.read_sync(scope)))
local success = store.write_sync(scope, data)
if success then
update_cache(data)
end
notify_save_result(success)
finish(success)
return
end
store.read(scope, {
on_done = function(read_data)
local data = upsert_session(migrate.ensure(read_data))
store.write(scope, data, {
on_done = function(success)
if success then
update_cache(data)
end
notify_save_result(success)
finish(success)
end,
})
end,
})
end
---Restore a named session from persistent storage
---@param name? string
---@param opts? { scope?: string, root_winid?: integer, silent?: boolean, on_done?: fun(restored: boolean) }
function M.restore(name, opts)
local silent = opts and opts.silent or false
local on_done = opts and opts.on_done or nil
local function finish(restored)
if on_done then
on_done(restored)
end
end
if not ensure_enabled(silent) then
finish(false)
return
end
local resolved_name = resolve_name(name)
local scope = SCOPE
store.read(scope, {
on_done = function(read_data)
local data = update_cache(read_data)
local session = data.sessions[resolved_name]
if not session or not session.items or #session.items == 0 then
if not silent then
vim.notify("No saved session: " .. resolved_name, vim.log.levels.INFO)
end
finish(false)
return
end
for _, item in ipairs(session.items) do
local loc = location.normalize({ uri = item.uri, range = item.range }, item.provider or "persist")
if loc then
stack.push(loc, { title = item.title })
end
end
if not silent then
vim.notify("Session restored: " .. resolved_name, vim.log.levels.INFO)
end
user_events.emit("PeekstackRestore", {
session = resolved_name,
item_count = #session.items,
})
finish(true)
end,
})
end
---List all saved sessions
---@param opts? { on_done?: fun(sessions: table<string, PeekstackSession>), silent?: boolean }
---@return table<string, PeekstackSession>
function M.list_sessions(opts)
local on_done = opts and opts.on_done or nil
local silent = opts and opts.silent
if silent == nil then
-- Synchronous list calls are mostly used for command completion.
-- Keep them silent to avoid notification spam when persist is disabled.
silent = on_done == nil
end
if not ensure_enabled(silent) then
return {}
end
if on_done then
store.read(SCOPE, {
on_done = function(read_data)
local data = update_cache(read_data)
on_done(data.sessions or {})
end,
})
elseif not cache_loaded then
update_cache(store.read_sync(SCOPE))
end
return cached_sessions
end
---Delete a named session
---@param name string
function M.delete_session(name)
if not ensure_enabled() then
return
end
local scope = SCOPE
store.read(scope, {
on_done = function(read_data)
local data = migrate.ensure(read_data)
if not data.sessions[name] then
vim.notify("Session not found: " .. name, vim.log.levels.WARN)
return
end
data.sessions[name] = nil
store.write(scope, data, {
on_done = function(success)
if success then
update_cache(data)
vim.notify("Session deleted: " .. name, vim.log.levels.INFO)
user_events.emit("PeekstackDeleteSession", {
session = name,
})
else
vim.notify("Failed to delete session: " .. name, vim.log.levels.WARN)
end
end,
})
end,
})
end
---Rename a session
---@param from string
---@param to string
function M.rename_session(from, to)
if not ensure_enabled() then
return
end
if from == to then
vim.notify("Source and destination names are the same", vim.log.levels.WARN)
return
end
local scope = SCOPE
store.read(scope, {
on_done = function(read_data)
local data = migrate.ensure(read_data)
if not data.sessions[from] then
vim.notify("Session not found: " .. from, vim.log.levels.WARN)
return
end
if data.sessions[to] then
vim.notify("Target session already exists: " .. to, vim.log.levels.WARN)
return
end
data.sessions[to] = data.sessions[from]
data.sessions[from] = nil
data.sessions[to].meta.updated_at = os.time()
store.write(scope, data, {
on_done = function(success)
if success then
update_cache(data)
vim.notify("Session renamed: " .. from .. " -> " .. to, vim.log.levels.INFO)
user_events.emit("PeekstackRenameSession", {
from = from,
to = to,
})
else
vim.notify("Failed to rename session: " .. from .. " -> " .. to, vim.log.levels.WARN)
end
end,
})
end,
})
end
---Reset in-memory session cache (for testing).
function M._reset_cache()
cached_sessions = {}
cache_loaded = false
end
return M