Skip to content

Commit 61aca7f

Browse files
authored
Merge pull request #6 from mhiro2/feat/stack-view-modules-position
feat(ui): Modularize stack view and harden config validation
2 parents cfacdb5 + cd2245b commit 61aca7f

17 files changed

Lines changed: 1823 additions & 1350 deletions

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ Configure via `require("peekstack").setup({ ... })`.
177177
base = "repo", -- "repo" | "cwd" | "absolute"
178178
max_width = 80,
179179
},
180+
stack_view = {
181+
position = "right", -- "left" | "right" | "bottom"
182+
},
180183
inline_preview = {
181184
enabled = true,
182185
max_lines = 10,

doc/peekstack.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ With options:
5454
base = "repo", -- "repo" | "cwd" | "absolute"
5555
max_width = 80,
5656
},
57+
stack_view = {
58+
position = "right", -- "left" | "right" | "bottom"
59+
},
5760
inline_preview = {
5861
enabled = true,
5962
max_lines = 10,
@@ -162,6 +165,8 @@ Commands are registered after `setup()` is called.
162165

163166
:PeekstackStack *:PeekstackStack*
164167
Open the stack view panel listing all active popups.
168+
Position is configurable via `ui.stack_view.position`
169+
(`left` / `right` / `bottom`).
165170
The focused popup is highlighted with a `▶` marker.
166171
Tree guides are shown for nested popup depth:
167172
`├` / `└` for sibling relation under the same parent.
@@ -342,6 +347,15 @@ BUFFER MODES *peekstack-buffer-modes*
342347
`ui.popup.source.confirm_on_close` prompts on close if modified
343348
`ui.popup.source.prevent_auto_close_if_modified` blocks auto-close
344349

350+
==============================================================================
351+
STACK VIEW *peekstack-stack-view*
352+
353+
`ui.stack_view.position` controls where the stack view panel opens:
354+
355+
"right" (default) right-side panel
356+
"left" left-side panel
357+
"bottom" bottom panel across full editor width
358+
345359
==============================================================================
346360
EVENTS *peekstack-events*
347361

lua/peekstack/commands.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,14 @@ function M.setup()
182182
"lsp.implementation",
183183
"lsp.references",
184184
"lsp.type_definition",
185+
"lsp.declaration",
185186
"diagnostics.under_cursor",
187+
"diagnostics.in_buffer",
186188
"file.under_cursor",
187189
"grep.search",
190+
"marks.buffer",
191+
"marks.global",
192+
"marks.all",
188193
}
189194
end,
190195
})

lua/peekstack/config.lua

Lines changed: 100 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,22 @@ local KNOWN_MARK_SCOPES = { "buffer", "global", "all" }
2222
---@type string[]
2323
local KNOWN_PATH_BASES = { "repo", "cwd", "absolute" }
2424

25+
---@type string[]
26+
local KNOWN_STACK_VIEW_POSITIONS = { "left", "right", "bottom" }
27+
2528
---@param path string
2629
---@param expected_type string
2730
---@param value any
28-
local function validate_type(path, expected_type, value)
31+
---@param default any
32+
---@return any
33+
local function validate_type(path, expected_type, value, default)
2934
if type(value) ~= expected_type then
30-
notify.warn(string.format("%s must be a %s, got %s", path, expected_type, type(value)))
35+
notify.warn(
36+
string.format("%s must be a %s, got %s. Falling back to %s", path, expected_type, type(value), tostring(default))
37+
)
38+
return default
3139
end
40+
return value
3241
end
3342

3443
---@param path string
@@ -103,6 +112,9 @@ M.defaults = {
103112
base = "repo",
104113
max_width = 80,
105114
},
115+
stack_view = {
116+
position = "right",
117+
},
106118
inline_preview = {
107119
enabled = true,
108120
max_lines = 10,
@@ -240,7 +252,8 @@ local function validate(cfg)
240252
end
241253

242254
if cfg.ui and cfg.ui.popup and cfg.ui.popup.editable ~= nil then
243-
validate_type("ui.popup.editable", "boolean", cfg.ui.popup.editable)
255+
cfg.ui.popup.editable =
256+
validate_type("ui.popup.editable", "boolean", cfg.ui.popup.editable, M.defaults.ui.popup.editable)
244257
end
245258

246259
if cfg.ui and cfg.ui.path then
@@ -258,6 +271,25 @@ local function validate(cfg)
258271
end
259272
end
260273

274+
if cfg.ui and cfg.ui.stack_view ~= nil then
275+
if type(cfg.ui.stack_view) ~= "table" then
276+
notify.warn(
277+
string.format("ui.stack_view must be a table, got %s. Falling back to defaults", type(cfg.ui.stack_view))
278+
)
279+
cfg.ui.stack_view = vim.deepcopy(M.defaults.ui.stack_view)
280+
else
281+
local stack_view = cfg.ui.stack_view
282+
if stack_view.position ~= nil then
283+
stack_view.position = validate_enum(
284+
"ui.stack_view.position",
285+
stack_view.position,
286+
KNOWN_STACK_VIEW_POSITIONS,
287+
M.defaults.ui.stack_view.position
288+
)
289+
end
290+
end
291+
end
292+
261293
-- Validate buffer_mode
262294
if cfg.ui and cfg.ui.popup and cfg.ui.popup.buffer_mode then
263295
cfg.ui.popup.buffer_mode = validate_enum(
@@ -272,10 +304,20 @@ local function validate(cfg)
272304
if cfg.ui and cfg.ui.popup and cfg.ui.popup.source then
273305
local source = cfg.ui.popup.source
274306
if source.prevent_auto_close_if_modified ~= nil then
275-
validate_type("ui.popup.source.prevent_auto_close_if_modified", "boolean", source.prevent_auto_close_if_modified)
307+
source.prevent_auto_close_if_modified = validate_type(
308+
"ui.popup.source.prevent_auto_close_if_modified",
309+
"boolean",
310+
source.prevent_auto_close_if_modified,
311+
M.defaults.ui.popup.source.prevent_auto_close_if_modified
312+
)
276313
end
277314
if source.confirm_on_close ~= nil then
278-
validate_type("ui.popup.source.confirm_on_close", "boolean", source.confirm_on_close)
315+
source.confirm_on_close = validate_type(
316+
"ui.popup.source.confirm_on_close",
317+
"boolean",
318+
source.confirm_on_close,
319+
M.defaults.ui.popup.source.confirm_on_close
320+
)
279321
end
280322
end
281323

@@ -303,7 +345,12 @@ local function validate(cfg)
303345
if cfg.ui and cfg.ui.inline_preview then
304346
local inline_preview = cfg.ui.inline_preview
305347
if inline_preview.enabled ~= nil then
306-
validate_type("ui.inline_preview.enabled", "boolean", inline_preview.enabled)
348+
inline_preview.enabled = validate_type(
349+
"ui.inline_preview.enabled",
350+
"boolean",
351+
inline_preview.enabled,
352+
M.defaults.ui.inline_preview.enabled
353+
)
307354
end
308355
if inline_preview.max_lines ~= nil then
309356
inline_preview.max_lines = validate_number_range(
@@ -314,7 +361,12 @@ local function validate(cfg)
314361
)
315362
end
316363
if inline_preview.hl_group ~= nil then
317-
validate_type("ui.inline_preview.hl_group", "string", inline_preview.hl_group)
364+
inline_preview.hl_group = validate_type(
365+
"ui.inline_preview.hl_group",
366+
"string",
367+
inline_preview.hl_group,
368+
M.defaults.ui.inline_preview.hl_group
369+
)
318370
end
319371
if inline_preview.close_events ~= nil then
320372
validate_event_list("ui.inline_preview.close_events", inline_preview.close_events)
@@ -336,7 +388,8 @@ local function validate(cfg)
336388
elseif type(cfg.ui.title.icons) == "table" then
337389
local icons = cfg.ui.title.icons
338390
if icons.enabled ~= nil then
339-
validate_type("ui.title.icons.enabled", "boolean", icons.enabled)
391+
icons.enabled =
392+
validate_type("ui.title.icons.enabled", "boolean", icons.enabled, M.defaults.ui.title.icons.enabled)
340393
end
341394
if icons.map ~= nil and type(icons.map) ~= "table" then
342395
notify.warn("ui.title.icons.map must be a table, got " .. type(icons.map) .. ". Falling back to defaults")
@@ -425,10 +478,16 @@ local function validate(cfg)
425478
validate_enum("providers.marks.scope", marks.scope, KNOWN_MARK_SCOPES, M.defaults.providers.marks.scope)
426479
end
427480
if marks.include ~= nil then
428-
validate_type("providers.marks.include", "string", marks.include)
481+
marks.include =
482+
validate_type("providers.marks.include", "string", marks.include, M.defaults.providers.marks.include)
429483
end
430484
if marks.include_special ~= nil then
431-
validate_type("providers.marks.include_special", "boolean", marks.include_special)
485+
marks.include_special = validate_type(
486+
"providers.marks.include_special",
487+
"boolean",
488+
marks.include_special,
489+
M.defaults.providers.marks.include_special
490+
)
432491
end
433492
end
434493

@@ -440,10 +499,20 @@ local function validate(cfg)
440499
if cfg.persist and cfg.persist.session then
441500
local session = cfg.persist.session
442501
if session.default_name ~= nil then
443-
validate_type("persist.session.default_name", "string", session.default_name)
502+
session.default_name = validate_type(
503+
"persist.session.default_name",
504+
"string",
505+
session.default_name,
506+
M.defaults.persist.session.default_name
507+
)
444508
end
445509
if session.prompt_if_missing ~= nil then
446-
validate_type("persist.session.prompt_if_missing", "boolean", session.prompt_if_missing)
510+
session.prompt_if_missing = validate_type(
511+
"persist.session.prompt_if_missing",
512+
"boolean",
513+
session.prompt_if_missing,
514+
M.defaults.persist.session.prompt_if_missing
515+
)
447516
end
448517
end
449518

@@ -453,25 +522,37 @@ local function validate(cfg)
453522
else
454523
local auto = cfg.persist.auto
455524
if auto.enabled ~= nil then
456-
validate_type("persist.auto.enabled", "boolean", auto.enabled)
525+
auto.enabled = validate_type("persist.auto.enabled", "boolean", auto.enabled, M.defaults.persist.auto.enabled)
457526
end
458527
if auto.session_name ~= nil then
459-
validate_type("persist.auto.session_name", "string", auto.session_name)
528+
auto.session_name =
529+
validate_type("persist.auto.session_name", "string", auto.session_name, M.defaults.persist.auto.session_name)
460530
end
461531
if auto.restore ~= nil then
462-
validate_type("persist.auto.restore", "boolean", auto.restore)
532+
auto.restore = validate_type("persist.auto.restore", "boolean", auto.restore, M.defaults.persist.auto.restore)
463533
end
464534
if auto.save ~= nil then
465-
validate_type("persist.auto.save", "boolean", auto.save)
535+
auto.save = validate_type("persist.auto.save", "boolean", auto.save, M.defaults.persist.auto.save)
466536
end
467537
if auto.restore_if_empty ~= nil then
468-
validate_type("persist.auto.restore_if_empty", "boolean", auto.restore_if_empty)
538+
auto.restore_if_empty = validate_type(
539+
"persist.auto.restore_if_empty",
540+
"boolean",
541+
auto.restore_if_empty,
542+
M.defaults.persist.auto.restore_if_empty
543+
)
469544
end
470545
if auto.debounce_ms ~= nil then
471-
validate_type("persist.auto.debounce_ms", "number", auto.debounce_ms)
546+
auto.debounce_ms =
547+
validate_type("persist.auto.debounce_ms", "number", auto.debounce_ms, M.defaults.persist.auto.debounce_ms)
472548
end
473549
if auto.save_on_leave ~= nil then
474-
validate_type("persist.auto.save_on_leave", "boolean", auto.save_on_leave)
550+
auto.save_on_leave = validate_type(
551+
"persist.auto.save_on_leave",
552+
"boolean",
553+
auto.save_on_leave,
554+
M.defaults.persist.auto.save_on_leave
555+
)
475556
end
476557
end
477558
end

lua/peekstack/core/popup.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ function M.create(location, opts)
132132
end
133133

134134
local source_bufnr = vim.fn.bufadd(fname)
135+
if source_bufnr == 0 then
136+
vim.notify("Failed to add buffer: " .. fname, vim.log.levels.WARN)
137+
return nil
138+
end
135139
local ok_load = pcall(vim.fn.bufload, source_bufnr)
136140
if not ok_load then
137141
vim.notify("Failed to load buffer: " .. fname, vim.log.levels.WARN)

lua/peekstack/core/stack.lua

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,6 @@ function M.current(winid)
180180
return stack.popups[#stack.popups]
181181
end
182182

183-
---@param id integer
184-
---@param winid? integer
185-
---@return boolean
186183
---@param stack PeekstackStackModel
187184
---@param idx integer
188185
---@param item PeekstackPopupModel

lua/peekstack/init.lua

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,14 +328,16 @@ function M.setup(opts)
328328
persist_auto.setup()
329329
end
330330

331-
---@return table
331+
---Proxy table for `peekstack.core.stack`.
332+
---@type table
332333
M.stack = setmetatable({}, {
333334
__index = function(_, k)
334335
return require("peekstack.core.stack")[k]
335336
end,
336337
})
337338

338-
---@return table
339+
---Proxy table for `peekstack.persist`.
340+
---@type table
339341
M.persist = setmetatable({}, {
340342
__index = function(_, k)
341343
return require("peekstack.persist")[k]

lua/peekstack/persist/init.lua

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ local function update_cache(data)
2222
end
2323

2424
---Check if persistence is enabled, notify if not
25-
---@return boolean
2625
---@param silent? boolean
2726
---@return boolean
2827
local function ensure_enabled(silent)

lua/peekstack/types.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,19 @@
142142
---@field root_winid integer
143143
---@field from_popup boolean
144144

145+
---@class PeekstackStackViewState
146+
---@field bufnr integer?
147+
---@field winid integer?
148+
---@field root_winid integer?
149+
---@field line_to_id table<integer, integer>
150+
---@field filter string?
151+
---@field header_lines integer
152+
---@field help_bufnr integer?
153+
---@field help_winid integer?
154+
---@field help_augroup integer?
155+
---@field autoclose_group integer?
156+
---@field autoclose_suspended integer
157+
145158
-- Config type definitions
146159

147160
---@class PeekstackConfigLayoutOffset
@@ -184,6 +197,9 @@
184197
---@field base "repo"|"cwd"|"absolute"
185198
---@field max_width integer
186199

200+
---@class PeekstackConfigStackView
201+
---@field position "left"|"right"|"bottom"
202+
187203
---@class PeekstackConfigInlinePreview
188204
---@field enabled boolean
189205
---@field max_lines integer
@@ -233,6 +249,7 @@
233249
---@field layout PeekstackConfigLayout
234250
---@field title PeekstackConfigTitle
235251
---@field path PeekstackConfigPath
252+
---@field stack_view PeekstackConfigStackView
236253
---@field inline_preview PeekstackConfigInlinePreview
237254
---@field quick_peek PeekstackConfigQuickPeek
238255
---@field popup PeekstackConfigPopup

0 commit comments

Comments
 (0)