Skip to content
Open
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
78 changes: 78 additions & 0 deletions lua/persistence/actions.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
local Popup = require("persistence.popup")

local M = {}

local state = {}
M.state = state

---@param win integer window id
---@return string
local function get_current_char(win)
local current_pos = vim.api.nvim_win_get_cursor(win)
local current_line = vim.api.nvim_get_current_line()
local current_char = current_line:match("(.)", current_pos[2] + 1)

return current_char
end

---Close the popup window according to `close_callback` if defined, otherwise bluntly closes the popup.
---`cascade` option only works with nested popups having `close_callback` set.
---@param opts nil|{cascade: boolean?}
function M.close_popup(opts)
local buf = vim.api.nvim_get_current_buf()
Popup.close(buf, opts)
end

---Delete selected sessions from given buffer of a popup
---@param sessions string[]
local function delete_selected_sessions(sessions)
for _, session in ipairs(sessions) do
local result = vim.fn.delete(session)
if result > 0 then
vim.notify("Failed to delete file: " .. session .. " , exited with status: " .. result, vim.log.levels.ERROR)
end
end
end

---Close the confirmation popup. The `confirm_close` popup behaves the same as `close_popup` but additionally
---deletes sessions that are removed from the buffer.
function M.confirm_close()
local buf = vim.api.nvim_get_current_buf()
local remove_sessions = (state[buf] or {}).remove_sessions or {}

delete_selected_sessions(remove_sessions)
Popup.close(buf, { cascade = true })
end

---Toggles cursor selection between Y and N actions
function M.toggle_action_selection()
local buf = vim.api.nvim_get_current_buf()

local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
local no_pos = lines[#lines]:find("%(N%)")
local yes_pos = lines[#lines]:find("%(Y%)")
local win = vim.api.nvim_get_current_win()

local current_char = get_current_char(win)
if current_char == "Y" then
vim.api.nvim_win_set_cursor(win, { #lines, no_pos })
elseif current_char == "N" then
vim.api.nvim_win_set_cursor(win, { #lines, yes_pos })
else
vim.api.nvim_win_set_cursor(win, { #lines, no_pos })
end
end

---Executes the action currently focused by the cursor. No-op if neither Y nor N is focused.
function M.execute_selected_action()
local win = vim.api.nvim_get_current_win()
local current_char = get_current_char(win)

if current_char == "Y" then
M.confirm_close()
elseif current_char == "N" then
M.close_popup({ cascade = false })
end
end

return M
74 changes: 74 additions & 0 deletions lua/persistence/config.lua
Original file line number Diff line number Diff line change
@@ -1,12 +1,86 @@
local actions = require("persistence.actions")

local M = {}

local map_opts = { noremap = false, silent = true }

---@class Persistence.WinOpts
---@field height number
---@field width number
---@field title string|nil
---@field border string|nil

---@class Persistence.ConfirmOpts
---@field width number
---@field title string|nil
---@field border string|nil

---@class Persistence.KeyMap
---@field mode string
---@field action string|fun()
---@field opts vim.keymap.set.Opts

---@class Persistence.ManageConfig
local manage = {
---@type Persistence.WinOpts
list = {
width = 100,
height = 30,
title = " Manage sessions ",
},
---@type Persistence.ConfirmOpts
confirm = {
width = 80,
title = " Confirm ",
},
---@type {list: {[string]: Persistence.KeyMap}, confirm: {[string]: Persistence.KeyMap}}
keymaps = {
list = {
q = {
mode = "n",
opts = map_opts,
action = actions.close_popup,
},
},
confirm = {
y = {
mode = "n",
opts = map_opts,
action = actions.confirm_close,
},
n = {
mode = "n",
opts = map_opts,
action = function()
actions.close_popup({ cascade = false })
end,
},
["<Tab>"] = {
mode = "n",
opts = map_opts,
action = actions.toggle_action_selection,
},
["<CR>"] = {
mode = "n",
opts = map_opts,
action = actions.execute_selected_action,
},
},
},
highlight = {
confirm_warning = "ErrorMsg",
neutral = "@label",
},
}

---@class Persistence.Config
local defaults = {
dir = vim.fn.stdpath("state") .. "/sessions/", -- directory where session files are saved
-- minimum number of file buffers that need to be open to save
-- Set to 0 to always save
need = 1,
branch = true, -- use git branch to save session
manage = manage,
}

---@type Persistence.Config
Expand Down
12 changes: 8 additions & 4 deletions lua/persistence/init.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
local Config = require("persistence.config")
local manage = require("persistence.manage")
local ops = require("persistence.ops")

local uv = vim.uv or vim.loop

Expand Down Expand Up @@ -137,10 +139,12 @@ end
--- get current branch name
---@return string?
function M.branch()
if uv.fs_stat(".git") then
local ret = vim.fn.systemlist("git branch --show-current")[1]
return vim.v.shell_error == 0 and ret or nil
end
ops.git_branch()
end

--- open manage sessions popup
function M.open_manage()
manage.open(M.list())
end

return M
171 changes: 171 additions & 0 deletions lua/persistence/manage.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
local Config = require("persistence.config")
local Popup = require("persistence.popup")
local ops = require("persistence.ops")
local state = require("persistence.actions").state

local M = {}

---Restore original window focus and cursor position
---@param current_win integer window id
---@param cursor_pos integer[] cursor position tuple
local function restore_window_cursor(current_win, cursor_pos)
vim.api.nvim_win_set_cursor(current_win, cursor_pos)
vim.fn.win_gotoid(current_win)
end

---@param sessions string[]
---@return string[]
local function to_session_files(sessions)
local files = {}
for _, session in ipairs(sessions) do
local name = ops.to_session_file_name(session)
table.insert(files, name)
end

return files
end

---@param parent_opts Persitence.Popup.CloseCallbackOpts
---@param entries string[]
---@param current_win integer
---@param cursor_pos integer[]
local function open_confirm_popup(parent_opts, entries, current_win, cursor_pos)
local lines = vim.api.nvim_buf_get_lines(parent_opts.buf, 0, -1, false)

local content = { "Are you sure you want to delete following sessions?" }

---@type string[]
local removed = vim
.iter(entries)
:filter(function(v)
return vim.iter(lines):any(function(l)
return l == v
end) == false
end)
:totable()
content = vim.list_extend(content, removed)
local confirm_actions = "(Y)es (N)o"
local padding = ""
for _ = 1, (Config.options.manage.confirm.width / 2 - (#confirm_actions / 2)) do
padding = padding .. " "
end
vim.list_extend(content, { "", padding .. confirm_actions })

if #removed > 0 then
local buf = Popup.open({
scratch = true,
ns = parent_opts.ns,
keymaps = Config.options.manage.keymaps.confirm,
---@diagnostic disable-next-line: assign-type-mismatch
win = vim.tbl_deep_extend("force", Config.options.manage.confirm, { height = #content }),
content = content,
prepare = function(o)
for i, line in ipairs(content) do
if i > 1 and i < (#content - 1) then
vim.api.nvim_buf_set_extmark(
o.buf,
o.ns,
i - 1,
0,
{ hl_group = Config.options.manage.highlight.confirm_warning, end_col = #line }
)
end
if i == #content then
local yes = line:find("%(Y%)")
vim.api.nvim_buf_set_extmark(
o.buf,
o.ns,
i - 1,
yes - 1,
{ hl_group = Config.options.manage.highlight.confirm_warning, end_col = yes + 4 }
)
local no = line:find("%(N%)")
vim.api.nvim_buf_set_extmark(
o.buf,
o.ns,
i - 1,
no - 1,
{ hl_group = Config.options.manage.highlight.neutral, end_col = no + 3 }
)
end
end
end,
after_win_create = function(o)
local no_pos = content[#content]:find("%(N%)")
vim.api.nvim_win_set_cursor(o.win, { #content, no_pos })
end,
close_callback = function(opts)
opts.close()
if opts.cascade then
parent_opts.close()
restore_window_cursor(current_win, cursor_pos)
end
end,
})

state[buf] = { remove_sessions = to_session_files(removed) }
else
-- nothing to remove, just close the thing
parent_opts.close()
restore_window_cursor(current_win, cursor_pos)
end
end

---@param ns integer
---@param entries string[]
---@param current_win integer
---@param cursor_pos integer[]
local function open_popup(ns, entries, current_win, cursor_pos)
Popup.open({
scratch = false,
ns = ns,
content = entries,
keymaps = Config.options.manage.keymaps.list,
win = Config.options.manage.list,
prepare = function(o)
for i, line in ipairs(entries) do
local branch_icon_index = line:find("()")
if branch_icon_index ~= nil then
local len = line:len()
if branch_icon_index ~= nil then
vim.api.nvim_buf_set_extmark(
o.buf,
o.ns,
i - 1,
branch_icon_index - 1,
{ hl_group = Config.options.manage.highlight.neutral, end_col = len }
)
end
end
end
end,
close_callback = function(opts)
open_confirm_popup(opts, entries, current_win, cursor_pos)
end,
})
end

---Open persistence session manager popup
---@param sessions string[]
function M.open(sessions)
local current_win = vim.api.nvim_get_current_win()
local cursor_pos = vim.api.nvim_win_get_cursor(current_win)

local ns = vim.api.nvim_create_namespace("persistence.manage")
local sessions_dir = Config.options.dir

---@type string[]
local entries = {}
for _, session in ipairs(sessions) do
if vim.uv.fs_stat(session) then
local file = session:sub(#sessions_dir + 1, -5)
local dir = ops.to_display_line(file)
table.insert(entries, dir)
else
table.insert(entries, session)
end
end
open_popup(ns, entries, current_win, cursor_pos)
end

return M
Loading