Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Built-in provider names:
- `:PeekstackSaveSession` — save current stack (persist enabled)
- `:PeekstackRestoreSession` — restore a saved session
- `:PeekstackListSessions` — list all saved sessions
- `:PeekstackDeleteSession {name}` — delete a saved session by name
- `:PeekstackDeleteSession [name]` — delete a saved session (prompts to select when no name is given)
- `:PeekstackRestorePopup` — restore the last closed popup (undo close)
- `:PeekstackRestoreAllPopups` — restore all closed popups
- `:PeekstackCloseAll` — close all popups in the current stack
Expand Down
5 changes: 3 additions & 2 deletions doc/peekstack.txt
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,9 @@ Commands are registered after `setup()` is called.
:PeekstackListSessions *:PeekstackListSessions*
List all saved sessions. Select one to view its summary.

:PeekstackDeleteSession {name} *:PeekstackDeleteSession*
Delete a saved session by name. Prompts for confirmation.
:PeekstackDeleteSession [name] *:PeekstackDeleteSession*
Delete a saved session. Prompts for confirmation before deleting. When
no name is given, prompts to select one from the saved sessions.

:PeekstackRestorePopup *:PeekstackRestorePopup*
Restore the last closed popup from history (undo close).
Expand Down
69 changes: 56 additions & 13 deletions lua/peekstack/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,37 @@ local function list_session_names()
return names
end

---Keep only candidates whose prefix matches the current argument.
---Lua command completion behaves like `customlist`, so Neovim does not filter
---the returned list against `ArgLead` for us.
---@param candidates string[]
---@param arg_lead string
---@return string[]
local function filter_by_prefix(candidates, arg_lead)
if not arg_lead or arg_lead == "" then
return candidates
end
return vim.tbl_filter(function(candidate)
return vim.startswith(candidate, arg_lead)
end, candidates)
end

---@param arg_lead string
---@return string[]
local function complete_session_names(arg_lead)
return filter_by_prefix(list_session_names(), arg_lead)
end

---Confirm and delete a named session.
---@param name string
local function confirm_delete_session(name)
vim.ui.select({ "Yes", "No" }, { prompt = "Delete session '" .. name .. "'?" }, function(choice)
if choice == "Yes" then
require("peekstack.persist").delete_session(name)
end
end)
end

function M.setup()
if loaded then
return
Expand Down Expand Up @@ -80,7 +111,7 @@ function M.setup()
require("peekstack.persist").restore(name)
end, {
nargs = "?",
complete = list_session_names,
complete = complete_session_names,
})

vim.api.nvim_create_user_command("PeekstackListSessions", function()
Expand Down Expand Up @@ -115,19 +146,31 @@ function M.setup()
end, {})

vim.api.nvim_create_user_command("PeekstackDeleteSession", function(opts)
local name = opts.args
if not name or name == "" then
notify.warn("Usage: PeekstackDeleteSession <name>")
local name = opts.args and opts.args ~= "" and opts.args or nil
if name then
confirm_delete_session(name)
return
end
vim.ui.select({ "Yes", "No" }, { prompt = "Delete session '" .. name .. "'?" }, function(choice)
if choice == "Yes" then
require("peekstack.persist").delete_session(name)
end
end)

require("peekstack.persist").list_sessions({
on_done = function(sessions)
local names = vim.tbl_keys(sessions)
table.sort(names)
if #names == 0 then
notify.info("No saved sessions")
return
end
vim.ui.select(names, { prompt = "Delete session" }, function(selected)
if not selected then
return
end
confirm_delete_session(selected)
end)
end,
})
end, {
nargs = 1,
complete = list_session_names,
nargs = "?",
complete = complete_session_names,
})

vim.api.nvim_create_user_command("PeekstackRestorePopup", function()
Expand Down Expand Up @@ -201,8 +244,8 @@ function M.setup()
require("peekstack").peek(provider, { mode = "quick" })
end, {
nargs = "?",
complete = function()
return require("peekstack").list_providers()
complete = function(arg_lead)
return filter_by_prefix(require("peekstack").list_providers(), arg_lead)
end,
})
end
Expand Down
120 changes: 120 additions & 0 deletions tests/commands_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ describe("peekstack.commands", function()
local peekstack = require("peekstack")
local persist = require("peekstack.persist")
local original_list_sessions = nil
local original_delete_session = nil
local original_select = nil
local original_notify = nil
local original_strftime = nil
Expand All @@ -12,6 +13,7 @@ describe("peekstack.commands", function()
config.setup({})
commands._reset()
original_list_sessions = persist.list_sessions
original_delete_session = persist.delete_session
original_select = vim.ui.select
original_notify = vim.notify
original_strftime = vim.fn.strftime
Expand All @@ -22,6 +24,9 @@ describe("peekstack.commands", function()
if original_list_sessions then
persist.list_sessions = original_list_sessions
end
if original_delete_session then
persist.delete_session = original_delete_session
end
if original_select then
vim.ui.select = original_select
end
Expand Down Expand Up @@ -54,6 +59,121 @@ describe("peekstack.commands", function()
assert.is_false(called_with_opts)
end)

it("filters session completion by ArgLead prefix", function()
persist.list_sessions = function()
return {
alpha = { items = {}, meta = { created_at = 1, updated_at = 1 } },
alpine = { items = {}, meta = { created_at = 1, updated_at = 1 } },
beta = { items = {}, meta = { created_at = 1, updated_at = 1 } },
}
end

commands.setup()
local names = vim.fn.getcompletion("PeekstackRestoreSession al", "cmdline")
table.sort(names)

assert.same({ "alpha", "alpine" }, names)
end)

it("filters quick peek completion by ArgLead prefix", function()
peekstack.setup({})
local names = vim.fn.getcompletion("PeekstackQuickPeek lsp.d", "cmdline")

assert.is_true(#names > 0)
for _, name in ipairs(names) do
assert.is_true(vim.startswith(name, "lsp.d"))
end
assert.is_true(vim.list_contains(names, "lsp.definition"))
assert.is_true(vim.list_contains(names, "lsp.declaration"))
assert.is_false(vim.list_contains(names, "lsp.references"))
end)

it("prompts to select a session when delete is invoked without a name", function()
local deleted = nil
local select_items = nil
persist.list_sessions = function(opts)
assert.is_truthy(opts)
assert.is_truthy(opts.on_done)
opts.on_done({
beta = { items = {}, meta = { created_at = 1, updated_at = 1 } },
alpha = { items = {}, meta = { created_at = 1, updated_at = 1 } },
})
return {}
end
persist.delete_session = function(name)
deleted = name
end

vim.ui.select = function(items, opts, on_choice)
if opts.prompt == "Delete session" then
select_items = vim.deepcopy(items)
on_choice("beta")
return
end
on_choice("Yes")
end

commands.setup()
vim.api.nvim_cmd({ cmd = "PeekstackDeleteSession" }, {})

assert.same({ "alpha", "beta" }, select_items)
assert.equals("beta", deleted)
end)

it("notifies when no sessions exist on nameless delete", function()
local deleted = false
local messages = {}
persist.list_sessions = function(opts)
opts.on_done({})
return {}
end
persist.delete_session = function()
deleted = true
end
vim.notify = function(msg)
table.insert(messages, msg)
end

commands.setup()
vim.api.nvim_cmd({ cmd = "PeekstackDeleteSession" }, {})

assert.is_true(vim.list_contains(messages, "[peekstack] No saved sessions"))
assert.is_false(deleted)
end)

it("confirms before deleting a named session", function()
local deleted = nil
local prompt = nil
persist.delete_session = function(name)
deleted = name
end
vim.ui.select = function(_items, opts, on_choice)
prompt = opts.prompt
on_choice("Yes")
end

commands.setup()
vim.api.nvim_cmd({ cmd = "PeekstackDeleteSession", args = { "alpha" } }, {})

assert.equals("Delete session 'alpha'?", prompt)
assert.equals("alpha", deleted)
end)

it("does not delete a named session when confirmation is declined", function()
local deleted = false
persist.delete_session = function()
deleted = true
end
vim.ui.select = function(_items, _opts, on_choice)
on_choice("No")
end

commands.setup()
vim.api.nvim_cmd({ cmd = "PeekstackDeleteSession", args = { "alpha" } }, {})

assert.is_false(deleted)
end)

it("handles missing session meta in list command", function()
local prompts = {}
persist.list_sessions = function(opts)
Expand Down