-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlayout.lua
More file actions
204 lines (182 loc) · 5.53 KB
/
layout.lua
File metadata and controls
204 lines (182 loc) · 5.53 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
local config = require("peekstack.config")
local M = {}
---@class PeekstackLayoutResult
---@field width integer
---@field height integer
---@field row integer
---@field col integer
---@field zindex integer
---@param value number
---@param min number
---@param max number
---@return number
local function clamp(value, min, max)
if value < min then
return min
end
if value > max then
return max
end
return value
end
---@param index integer
---@return PeekstackLayoutResult
function M.compute(index)
local ui = config.get().ui
local layout = ui.layout
local columns = vim.o.columns
local lines = vim.o.lines - vim.o.cmdheight
local max_w = math.floor(columns * layout.max_ratio)
local max_h = math.floor(lines * layout.max_ratio)
local base_width = clamp(max_w, layout.min_size.w, columns)
local base_height = clamp(max_h, layout.min_size.h, lines)
local step = index - 1
local style = layout.style or "stack"
local valid_styles = { stack = true, cascade = true, single = true }
if not valid_styles[style] then
style = "stack"
end
local width = base_width
local height = base_height
if style == "stack" then
width = clamp(base_width - (layout.shrink.w * step), layout.min_size.w, columns)
height = clamp(base_height - (layout.shrink.h * step), layout.min_size.h, lines)
end
local row = math.max(math.floor((lines - height) / 2), 0)
local col = math.max(math.floor((columns - width) / 2), 0)
if style == "stack" or style == "cascade" then
row = row + (layout.offset.row * step)
col = col + (layout.offset.col * step)
end
return {
width = width,
height = height,
row = row,
col = col,
zindex = layout.zindex_base + step,
}
end
---Compute fullscreen layout for zoomed popup.
---@param popup_count integer number of popups in the stack
---@return PeekstackLayoutResult
function M.compute_zoom(popup_count)
local columns = vim.o.columns
local lines = vim.o.lines - vim.o.cmdheight
local base = config.get().ui.layout.zindex_base
return {
width = columns,
height = lines,
row = 0,
col = 0,
zindex = base + popup_count + 1,
}
end
---@param winid integer
---@param is_focused boolean
local function set_popup_winhighlight(winid, is_focused)
if not vim.api.nvim_win_is_valid(winid) then
return
end
vim.wo[winid].winhighlight = is_focused and "FloatBorder:PeekstackPopupBorderFocused"
or "FloatBorder:PeekstackPopupBorder"
end
---@param stack PeekstackStackModel
---@return integer?
local function focused_popup_winid(stack)
local winid = vim.api.nvim_get_current_win()
for _, popup in ipairs(stack.popups) do
if popup.winid == winid then
return winid
end
end
return nil
end
---@param winid integer
---@param is_zoomed boolean
local function set_popup_zoom_winhighlight(winid, is_zoomed)
if not vim.api.nvim_win_is_valid(winid) then
return
end
if is_zoomed then
vim.wo[winid].winhighlight = "FloatBorder:PeekstackPopupBorderZoomed"
end
end
---@param stack PeekstackStackModel
function M.reflow(stack)
local focused_winid = focused_popup_winid(stack)
local base = config.get().ui.layout.zindex_base
local top = base + #stack.popups
local zoomed_id = stack.zoomed_id
for idx, popup in ipairs(stack.popups) do
if popup.winid and vim.api.nvim_win_is_valid(popup.winid) then
local is_focused = focused_winid ~= nil and popup.winid == focused_winid
local is_zoomed = zoomed_id ~= nil and popup.id == zoomed_id
local lo
if is_zoomed then
lo = M.compute_zoom(#stack.popups)
else
lo = M.compute(idx)
end
local z = lo.zindex
if not is_zoomed and is_focused then
z = top
end
local win_opts = vim.tbl_extend("force", popup.win_opts or {}, {
row = lo.row,
col = lo.col,
width = lo.width,
height = lo.height,
zindex = z,
})
vim.api.nvim_win_set_config(popup.winid, win_opts)
if is_zoomed then
set_popup_zoom_winhighlight(popup.winid, true)
else
set_popup_winhighlight(popup.winid, is_focused)
end
end
end
end
---Temporarily raise the focused popup to the foreground while keeping
---all other popups at their natural zindex. Uses the same layout
---computation as reflow so the config passed to nvim_win_set_config is
---always in a known-good format (avoids nvim_win_get_config round-trip
---issues across Neovim versions).
---@param stack PeekstackStackModel
---@param focused_winid integer
function M.update_focus_zindex(stack, focused_winid)
local ui = config.get().ui
local base = ui.layout.zindex_base
local top = base + #stack.popups
local zoomed_id = stack.zoomed_id
for idx, popup in ipairs(stack.popups) do
if popup.winid and vim.api.nvim_win_is_valid(popup.winid) then
local is_focused = popup.winid == focused_winid
local is_zoomed = zoomed_id ~= nil and popup.id == zoomed_id
local lo
if is_zoomed then
lo = M.compute_zoom(#stack.popups)
else
lo = M.compute(idx)
end
local z = lo.zindex
if not is_zoomed and is_focused then
z = top
end
local win_opts = vim.tbl_extend("force", popup.win_opts or {}, {
row = lo.row,
col = lo.col,
width = lo.width,
height = lo.height,
zindex = z,
})
pcall(vim.api.nvim_win_set_config, popup.winid, win_opts)
if is_zoomed then
set_popup_zoom_winhighlight(popup.winid, true)
else
set_popup_winhighlight(popup.winid, is_focused)
end
end
end
end
return M