-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhistory.lua
More file actions
238 lines (217 loc) · 6.83 KB
/
history.lua
File metadata and controls
238 lines (217 loc) · 6.83 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
local M = {}
local DEFAULT_MAX_HISTORY = 50
-- Lazy-loaded dependencies (same pattern as stack.lua).
local config, layout, popup, user_events
local function deps()
if not config then
config = require("peekstack.config")
layout = require("peekstack.core.layout")
popup = require("peekstack.core.popup")
user_events = require("peekstack.core.user_events")
end
end
---Build a history entry from a popup model.
---@param item PeekstackPopupModel
---@param idx integer position in the stack at close time
---@return PeekstackHistoryEntry
function M.build_entry(item, idx)
return {
popup_id = item.id,
location = item.location,
title = item.title,
title_chunks = item.title_chunks,
pinned = item.pinned,
buffer_mode = item.buffer_mode or "copy",
source_bufnr = item.source_bufnr,
created_at = item.created_at,
closed_at = os.time(),
restore_index = idx,
parent_popup_id = item.parent_popup_id,
}
end
---Insert a history entry into the stack and enforce max_items limit.
---@param stack PeekstackStackModel
---@param entry PeekstackHistoryEntry
function M.push_entry(stack, entry)
deps()
local max_history = config.get().ui.popup.history.max_items or DEFAULT_MAX_HISTORY
table.insert(stack.history, entry)
if #stack.history > max_history then
table.remove(stack.history, 1)
end
end
---@param event string
---@param popup_model PeekstackPopupModel
---@param root_winid integer
local function emit_popup_event(event, popup_model, root_winid)
deps()
user_events.emit(event, user_events.build_popup_data(popup_model, root_winid))
end
---Check whether a popup with the given id exists in the stack.
---@param stack PeekstackStackModel
---@param popup_id integer
---@return boolean
local function popup_exists_in_stack(stack, popup_id)
for _, p in ipairs(stack.popups) do
if p.id == popup_id then
return true
end
end
return false
end
---Resolve parent_popup_id for a history entry being restored.
---Uses id_remap (for bulk restore) and verifies the target exists.
---@param stack PeekstackStackModel
---@param entry PeekstackHistoryEntry
---@param id_remap? table<integer, integer>
---@return integer?
local function resolve_parent_id(stack, entry, id_remap)
local parent_id = entry.parent_popup_id
if not parent_id then
return nil
end
if id_remap and id_remap[parent_id] then
parent_id = id_remap[parent_id]
end
if popup_exists_in_stack(stack, parent_id) then
return parent_id
end
return nil
end
---Return the stack-level id remap table, creating it if needed.
---@param stack PeekstackStackModel
---@return table<integer, integer>
local function ensure_id_remap(stack)
if not stack._id_remap then
stack._id_remap = {}
end
return stack._id_remap
end
---Record the mapping from old popup_id to new model id.
---@param stack PeekstackStackModel
---@param entry PeekstackHistoryEntry
---@param model PeekstackPopupModel
local function record_remap(stack, entry, model)
if entry.popup_id then
ensure_id_remap(stack)[entry.popup_id] = model.id
end
end
---Restore a history entry into the stack.
---@param stack PeekstackStackModel
---@param entry PeekstackHistoryEntry
---@param id_remap? table<integer, integer> extra old popup id -> new popup id mapping
---@return PeekstackPopupModel?
function M.restore_entry(stack, entry, id_remap)
deps()
-- Merge stack-level remap with any caller-provided remap.
local merged = ensure_id_remap(stack)
if id_remap then
for k, v in pairs(id_remap) do
merged[k] = v
end
end
local create_opts = {
buffer_mode = entry.buffer_mode or "copy",
origin_winid = stack.root_winid,
parent_popup_id = resolve_parent_id(stack, entry, merged),
}
-- Only pass title override for user-renamed popups (no title_chunks).
-- Auto-generated titles are regenerated by build_title() to preserve
-- structured chunks for popup window and stack view highlighting.
if not entry.title_chunks then
create_opts.title = entry.title
end
-- For source mode, check if the source buffer is still valid
if create_opts.buffer_mode == "source" and entry.source_bufnr then
if not vim.api.nvim_buf_is_valid(entry.source_bufnr) then
-- Fallback to copy mode if source buffer is gone
create_opts.buffer_mode = "copy"
end
end
local model = popup.create(entry.location, create_opts)
if not model then
return nil
end
model.pinned = entry.pinned or false
local restore_position = config.get().ui.popup.history.restore_position or "top"
if restore_position == "original" and entry.restore_index then
local insert_idx = math.min(entry.restore_index, #stack.popups + 1)
table.insert(stack.popups, insert_idx, model)
else
table.insert(stack.popups, model)
end
stack.focused_id = model.id
layout.reflow(stack)
emit_popup_event("PeekstackPush", model, stack.root_winid)
user_events.emit("PeekstackRestorePopup", user_events.build_popup_data(model, stack.root_winid))
return model
end
---Restore the last closed popup from history (undo close).
---@param stack PeekstackStackModel
---@return PeekstackPopupModel?
function M.restore_last(stack)
if #stack.history == 0 then
return nil
end
local entry = stack.history[#stack.history]
local restored = M.restore_entry(stack, entry)
if not restored then
return nil
end
record_remap(stack, entry, restored)
table.remove(stack.history)
return restored
end
---Restore all closed popups from history.
---@param stack PeekstackStackModel
---@return PeekstackPopupModel[]
function M.restore_all(stack)
local restored = {}
local remaining = {}
---@type table<integer, integer>
local id_remap = {}
while #stack.history > 0 do
local entry = table.remove(stack.history)
local model = M.restore_entry(stack, entry, id_remap)
if model and entry.popup_id then
id_remap[entry.popup_id] = model.id
table.insert(restored, model)
elseif model then
table.insert(restored, model)
else
table.insert(remaining, 1, entry)
end
end
stack.history = remaining
return restored
end
---Restore a specific history entry by index back into the stack.
---@param stack PeekstackStackModel
---@param idx integer index in the history list (1-based)
---@return PeekstackPopupModel?
function M.restore_from_history(stack, idx)
if idx < 1 or idx > #stack.history then
return nil
end
local entry = stack.history[idx]
local restored = M.restore_entry(stack, entry)
if not restored then
return nil
end
record_remap(stack, entry, restored)
table.remove(stack.history, idx)
return restored
end
---Get the history list for a stack.
---@param stack PeekstackStackModel
---@return PeekstackHistoryEntry[]
function M.list(stack)
return stack.history
end
---Clear the history for a stack.
---@param stack PeekstackStackModel
function M.clear(stack)
stack.history = {}
stack._id_remap = nil
end
return M