Skip to content

Commit 71c8af9

Browse files
authored
Merge pull request #425 from cameronr/purge_old_sessions
feat: support for purging old sessions
2 parents 095b0b5 + 14ce29e commit 71c8af9

File tree

9 files changed

+147
-8
lines changed

9 files changed

+147
-8
lines changed

.github/workflows/tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ jobs:
2626
strategy:
2727
matrix:
2828
os: [ubuntu-latest]
29-
neovim_version: ["v0.7.2", "v0.8.3", "v0.9.5", "v0.10.3", "nightly"]
29+
neovim_version: ["v0.7.2", "v0.9.5", "v0.10.3", "v0.11.0", "nightly"]
3030
include:
3131
- os: windows-latest
32-
neovim_version: v0.10.3
32+
neovim_version: v0.11.0
3333
runs-on: ${{ matrix.os }}
3434

3535
steps:

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Here are the default settings:
7575
cwd_change_handling = false, -- Follow cwd changes, saving a session before change and restoring after
7676
lsp_stop_on_restore = false, -- Should language servers be stopped when restoring a session. Can also be a function that will be called if set. Not called on autorestore from startup
7777
restore_error_handler = nil, -- Called when there's an error restoring. By default, it ignores fold errors otherwise it displays the error and returns false to disable auto_save
78+
purge_after_minutes = nil, -- Sessions older than purge_after_minutes will be deleted asynchronously on startup, e.g. set to 14400 to delete sessions that haven't been accessed for more than 10 days, defaults to off (no purging), requires >= nvim 0.10
7879
log_level = "error", -- Sets the log level of the plugin (debug, info, warn, error).
7980

8081
session_lens = {
@@ -500,5 +501,5 @@ Neovim > 0.7
500501
Tested with:
501502

502503
```
503-
NVIM v0.7.2 - NVIM 0.10.1
504+
NVIM v0.7.2 - NVIM 0.11.0
504505
```

doc/auto-session.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ AutoSession.Config *AutoSession.Config*
3333
{lsp_stop_on_restore?} (boolean|function) Should language servers be stopped when restoring a session. Can also be a function that will be called if set. Not called on autorestore from startup
3434
{restore_error_handler?} (restore_error_fn) Called when there's an error restoring. By default, it ignores fold errors otherwise it displays the error and returns false to disable auto_save
3535

36+
{purge_after_minutes?} (number|nil) -- Sessions older than purge_after_minutes will be deleted asynchronously on startup, e.g. set to 14400 to delete sessions that haven't been accessed for more than 10 days, defaults to off (no purging), requires >= nvim 0.10
3637
{session_lens?} (SessionLens) Session lens configuration options
3738

3839
{pre_save_cmds?} (table) executes before a session is saved

lua/auto-session/autocmds.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,22 +389,22 @@ function M.setup_autocmds(AutoSession)
389389

390390
if not Config.lazy_support then
391391
-- If auto_restore_lazy_delay_enabled is false, just restore the session as normal
392-
AutoSession.auto_restore_session_at_vim_enter()
392+
AutoSession.start()
393393
return
394394
end
395395

396396
-- Not in pager mode, auto_restore_lazy_delay_enabled is true, check for Lazy
397397
local ok, lazy_view = pcall(require, "lazy.view")
398398
if not ok then
399399
-- No Lazy, load as usual
400-
AutoSession.auto_restore_session_at_vim_enter()
400+
AutoSession.start()
401401
return
402402
end
403403

404404
if not lazy_view.visible() then
405405
-- Lazy isn't visible, load as usual
406406
Lib.logger.debug "Lazy is loaded, but not visible, will try to restore session"
407-
AutoSession.auto_restore_session_at_vim_enter()
407+
AutoSession.start()
408408
return
409409
end
410410

@@ -449,7 +449,7 @@ function M.setup_autocmds(AutoSession)
449449
-- Schedule restoration for the next pass in the event loop to time for the window to close
450450
-- Not doing this could create a blank buffer in the restored session
451451
vim.schedule(function()
452-
AutoSession.auto_restore_session_at_vim_enter()
452+
AutoSession.start()
453453
end)
454454
end,
455455
})

lua/auto-session/config.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ local M = {}
2929
---@field lsp_stop_on_restore? boolean|function Should language servers be stopped when restoring a session. Can also be a function that will be called if set. Not called on autorestore from startup
3030
---
3131
---@field restore_error_handler? restore_error_fn Called when there's an error restoring. By default, it ignores fold errors otherwise it displays the error and returns false to disable auto_save
32+
---@field purge_after_minutes? number|nil -- Sessions older than purge_after_minutes will be deleted asynchronously on startup, e.g. set to 14400 to delete sessions that haven't been accessed for more than 10 days, defaults to off (no purging), requires >= nvim 0.10
3233
---
3334
---@field session_lens? SessionLens Session lens configuration options
3435
---
@@ -87,6 +88,7 @@ local defaults = {
8788
cwd_change_handling = false, -- Follow cwd changes, saving a session before change and restoring after
8889
lsp_stop_on_restore = false, -- Should language servers be stopped when restoring a session. Can also be a function that will be called if set. Not called on autorestore from startup
8990
restore_error_handler = nil, -- Called when there's an error restoring. By default, it ignores fold errors otherwise it displays the error and returns false to disable auto_save
91+
purge_after_minutes = nil, -- Sessions older than purge_after_minutes will be deleted asynchronously on startup, e.g. set to 14400 to delete sessions that haven't been accessed for more than 10 days, defaults to off (no purging), requires >= nvim 0.10
9092
log_level = "error", -- Sets the log level of the plugin (debug, info, warn, error).
9193

9294
---@type SessionLens
@@ -236,6 +238,10 @@ function M.check(logger)
236238
logger.warn "vim.o.sessionoptions is missing localoptions. \nUse `:checkhealth autosession` for more info."
237239
end
238240

241+
if M.purge_after_minutes and vim.fn.has "nvim-0.10" ~= 1 then
242+
logger.warn "the purge_after_minutes options requires nvim >= 0.10"
243+
end
244+
239245
-- TODO: At some point, we should pop up a warning about old config if
240246
-- M.has_old_config but let's make sure everything is working well before doing that
241247
end

lua/auto-session/health.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,17 @@ local function check_lazy_settings()
7373
end
7474
end
7575

76+
function check_features()
77+
if Config.purge_after_minutes and vim.fn.has "nvim-0.10" ~= 1 then
78+
warn "The purge_after_minutes config option requires nvim 0.10 or greater to work"
79+
end
80+
end
81+
7682
function M.check()
7783
start "vim options"
7884
check_session_options()
7985
check_lazy_settings()
86+
check_features()
8087

8188
start "Config"
8289
if Config.has_old_config then

lua/auto-session/init.lua

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,12 +425,37 @@ function AutoSession.AutoRestoreSession(session_name, is_startup)
425425
return AutoSession.RestoreSession(session_name, opts)
426426
end
427427

428+
---@private
429+
---Called at VimEnter to start AutoSession
430+
---Will call auto_restore_session_at_vim_enter and also purge sessions (if enabled)
431+
---@return boolean # Was a session restored
432+
function AutoSession.start()
433+
local did_auto_restore = AutoSession.auto_restore_session_at_vim_enter()
434+
435+
if Config.purge_after_minutes then
436+
local work = vim.uv.new_work(Lib.purge_old_sessions, function(purged_sessions_json)
437+
vim.schedule(function()
438+
local purged_sessions = vim.json.decode(purged_sessions_json)
439+
if not vim.tbl_isempty(purged_sessions) then
440+
Lib.logger.info(
441+
"Deleted old sessions:\n"
442+
.. table.concat(vim.tbl_map(Lib.escaped_session_name_to_session_name, purged_sessions), "\n")
443+
)
444+
end
445+
end)
446+
end)
447+
work:queue(AutoSession.get_root_dir(), Config.purge_after_minutes)
448+
end
449+
450+
return did_auto_restore
451+
end
452+
428453
---@private
429454
---Called at VimEnter (after Lazy is done) to see if we should automatically restore a session
430455
---If launched with a single directory parameter and Config.args_allow_single_directory is true, pass
431456
---that in as the session_dir. Handles both 'nvim .' and 'nvim some/dir'
432457
---Also make sure to call no_restore if no session was restored
433-
---@return boolean Was a session restored
458+
---@return boolean # Was a session restored
434459
function AutoSession.auto_restore_session_at_vim_enter()
435460
-- Save the launch args here as restoring a session will replace vim.fn.argv. We clear
436461
-- launch_argv in restore session so it's only used for the session launched from the command

lua/auto-session/lib.lua

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,4 +758,38 @@ function Lib.combine_session_name_with_git_branch(session_name, git_branch_name,
758758
return session_name .. "|" .. git_branch_name
759759
end
760760

761+
---Delete sessions that have access times older than purge_days_old old
762+
---@param session_dir string The session directory to look for sessions in
763+
---@param purge_older_than_minutes number in minutes, e.g. 14400, delete sessions older than 10 days ago
764+
---@return string # json encoded string of escaped session filenames removed
765+
function Lib.purge_old_sessions(session_dir, purge_older_than_minutes)
766+
local epoch = os.time()
767+
local garbage_collect_seconds = purge_older_than_minutes * 60
768+
local scan_dir = assert(vim.uv.fs_scandir(session_dir))
769+
local out = {}
770+
771+
if purge_older_than_minutes == 0 or garbage_collect_seconds == 0 then
772+
return "[]"
773+
end
774+
775+
local file = vim.uv.fs_scandir_next(scan_dir)
776+
while file do
777+
local abs_path = session_dir .. file
778+
local fd = assert(vim.uv.fs_open(abs_path, "r", 0))
779+
local stat = assert(vim.uv.fs_fstat(fd))
780+
local atime = stat["atime"]["sec"]
781+
assert(vim.uv.fs_close(fd))
782+
local age = epoch - atime
783+
-- print("file: " .. abs_path .. " age: " .. age)
784+
if age > garbage_collect_seconds then
785+
assert(vim.uv.fs_unlink(abs_path))
786+
table.insert(out, file)
787+
end
788+
789+
file = vim.uv.fs_scandir_next(scan_dir)
790+
end
791+
792+
return vim.json.encode(out)
793+
end
794+
761795
return Lib

tests/purge_old_sessions_spec.lua

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---@diagnostic disable: undefined-field
2+
local TL = require "tests/test_lib"
3+
local stub = require "luassert.stub"
4+
5+
describe("Setting purge_after_minutes", function()
6+
local as = require "auto-session"
7+
8+
-- old session 10 days ago
9+
local old_session = 10
10+
11+
as.setup {
12+
purge_after_minutes = 60 * 24 * 3,
13+
}
14+
15+
TL.clearSessionFilesAndBuffers()
16+
vim.cmd("e " .. TL.test_file)
17+
18+
-- requires nvim >= 0.10
19+
if vim.fn.has "nvim-0.10" == 1 then
20+
it("does purge old sessions while leaving recent ones", function()
21+
as.SaveSession()
22+
as.SaveSession(TL.named_session_name)
23+
24+
assert.equals(1, vim.fn.filereadable(TL.default_session_path))
25+
assert.equals(1, vim.fn.filereadable(TL.named_session_path))
26+
27+
-- Make the named session file appear to be 10 days old
28+
local current_time = os.time()
29+
local old_time = current_time - (old_session * 24 * 60 * 60) -- 10 days ago in seconds
30+
31+
-- set the modified and accessed time
32+
vim.uv.fs_utime(TL.named_session_path, old_time, old_time)
33+
34+
-- Verify the file's modification time is now old
35+
local file_time = vim.fn.getftime(TL.named_session_path)
36+
assert.is_true(file_time < current_time - (old_session * 24 * 60 * 60) + 60) -- Add 60 seconds tolerance
37+
38+
-- Hook into decode to capture when function finishes
39+
local json_decode = vim.json.decode
40+
local purge_finished = false
41+
stub(vim.json, "decode", function(str)
42+
-- session control also uses json functions so we're only looking for json returned by
43+
-- Lib.purge_old_sessions
44+
if str == '["' .. TL.named_session_name .. '.vim"]' then
45+
purge_finished = true
46+
end
47+
return json_decode(str)
48+
end)
49+
50+
-- Now purge old sessions
51+
as.start()
52+
53+
vim.wait(1000, function()
54+
return purge_finished
55+
end)
56+
57+
-- Revert the stub
58+
vim.json.decode:revert()
59+
60+
-- The named session should be deleted, but the default session should remain
61+
assert.equals(1, vim.fn.filereadable(TL.default_session_path))
62+
assert.equals(0, vim.fn.filereadable(TL.named_session_path))
63+
end)
64+
end
65+
end)

0 commit comments

Comments
 (0)