Skip to content
Closed
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
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Neovim Session Manager

A Neovim plugin that use built-in `:mksession` to manage sessions like folders in VSCode. It allows you to save the current folder as a session to open it later. The plugin can also automatically load the last session on startup, save the current one on exit and switch between session folders.
A Neovim plugin that use built-in `:mksession`/[resession.nvim](https://github.com/stevearc/resession.nvim) to manage sessions like folders in VSCode. It allows you to save the current folder as a session to open it later. The plugin can also automatically load the last session on startup, save the current one on exit and switch between session folders.

The plugin saves the sessions in the specified folder (see [configuration](#configuration)). The session corresponds to the working directory. If a session already exists for the current folder, it will be overwritten.

## Dependencies

- [plenary.nvim](https://github.com/nvim-lua/plenary.nvim) for internal helpers.
- [resession.nvim](https://github.com/nvim-lua/plenary.nvim) (optional) if using `resession` backend.

## Commands

Expand Down Expand Up @@ -34,6 +35,7 @@ To configure the plugin, you can call `require('session_manager').setup(values)`
local Path = require('plenary.path')
local config = require('session_manager.config')
require('session_manager').setup({
resession_backend = false, -- Use resession.nvim as backend instead of vim's built-in :mksession
sessions_dir = Path:new(vim.fn.stdpath('data'), 'sessions'), -- The directory where the session files will be saved.
session_filename_to_dir = session_filename_to_dir, -- Function that replaces symbols into separators and colons to transform filename into a session directory.
dir_to_session_filename = dir_to_session_filename, -- Function that replaces separators and colons into special symbols to transform session directory into a filename. Should use `vim.uv.cwd()` if the passed `dir` is `nil`.
Expand Down Expand Up @@ -116,6 +118,35 @@ vim.api.nvim_create_autocmd({ 'BufWritePre' }, {
})
```

## Resession backend

If you want to use `resession.nvim` as a backend instead of the built-in `:mksession`, set `resession_backend` to `true` in the configuration. Make sure you have `resession.nvim` installed.

Session-manager Config
```lua
{
"Shatur/neovim-session-manager",
lazy = false,
dependencies = {
{
"stevearc/resession.nvim",
lazy = false,
config = true,
},
},
config = function()
require("session_manager").setup({
-- ...
})
end,
},
```
for session saving config, sen [resession-session-options](https://github.com/stevearc/resession.nvim#setup-options)

NOTE: if you are using `resession.nvim` as a backend, the `config.autosave_ignore_filetypes` and `config.autosave_ignore_buftypes` options will be ignored, you should use `resession.nvim`'s `buf_filter` options instead.

## Additional resources

For more information about autocmd and its event, see also:

- [`:help autocmd`](https://neovim.io/doc/user/autocmd.html)
Expand Down
40 changes: 33 additions & 7 deletions lua/session_manager/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ local Enum = require('plenary.enum')
local path_replacer = '__'
local colon_replacer = '++'

local resession_replacer = '_'

---@class SessionManagerConfig: SessionManagerConfig.default
local config = {
---@class Mode
---@class AutoloadMode: Enum
---@field Disabled Mode
---@field CurrentDir Mode
---@field LastSession Mode
---@field GitSession Mode
AutoloadMode = Enum({
'Disabled',
'CurrentDir',
Expand All @@ -18,35 +27,52 @@ local config = {
---@return table: Session directory
local function session_filename_to_dir(filename)
-- Get session filename.
local dir = filename:sub(#tostring(config.sessions_dir) + 2)
if config.resession_backend then
config.defaults.sessions_dir = Path:new(vim.fn.stdpath('data'), 'resession')
local dir = filename:sub(#tostring(config.sessions_dir) + 2, -6)
dir = dir:gsub('__', ':' .. Path.path.sep):gsub(resession_replacer, Path.path.sep):gsub('++', '_')
return Path:new(dir)
else
local dir = filename:sub(#tostring(config.sessions_dir) + 2)

dir = dir:gsub(colon_replacer, ':')
dir = dir:gsub(path_replacer, Path.path.sep)
return Path:new(dir)
dir = dir:gsub(colon_replacer, ':')
dir = dir:gsub(path_replacer, Path.path.sep)
return Path:new(dir)
end
end

--- Replaces separators and colons into special symbols to transform session directory into a filename.
---@param dir string: Path to session directory.
---@return table: Session filename.
local function dir_to_session_filename(dir)
local filename = dir:gsub(':', colon_replacer)
filename = filename:gsub(Path.path.sep, path_replacer)
return Path:new(config.sessions_dir):joinpath(filename)
if config.resession_backend then
local filename = string.format('%s.json', dir:gsub('_', '++'):gsub(Path.path.sep, '_'):gsub(':', '_'))
return Path:new(config.sessions_dir):joinpath(filename)
else
local filename = dir:gsub(':', colon_replacer)
filename = filename:gsub(Path.path.sep, path_replacer)
return Path:new(config.sessions_dir):joinpath(filename)
end
end

---@class SessionManagerConfig.default
config.defaults = {
resession_backend = false,
sessions_dir = Path:new(vim.fn.stdpath('data'), 'sessions'),
session_filename_to_dir = session_filename_to_dir,
dir_to_session_filename = dir_to_session_filename,
---@type Mode|Mode[]
autoload_mode = config.AutoloadMode.LastSession,
autosave_last_session = true,
autosave_ignore_not_normal = true,
autosave_ignore_dirs = {},
---@type string[] All buffers of these file types will be closed before the session is saved.
autosave_ignore_filetypes = {
'gitcommit',
'gitrebase',
},
autosave_ignore_buftypes = {},
---@type boolean Always autosaves session. If true, only autosaves after a session is active.
autosave_only_in_session = false,
max_path_length = 80,
load_include_current = false,
Expand Down
2 changes: 1 addition & 1 deletion lua/session_manager/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ local Job = require('plenary.job')
local session_manager = {}

--- Apply user settings.
---@param values table
---@param values SessionManagerConfig
function session_manager.setup(values) setmetatable(config, { __index = vim.tbl_extend('force', config.defaults, values) }) end

-- Displays action selection menu for :SessionManager
Expand Down
65 changes: 44 additions & 21 deletions lua/session_manager/utils.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---@diagnostic disable: deprecated
local config = require('session_manager.config')
local scandir = require('plenary.scandir')
local Path = require('plenary.path')
Expand Down Expand Up @@ -47,24 +48,31 @@ function utils.load_session(filename, discard_current)
end
end

-- Delete all buffers first except the current one to avoid entering buffers scheduled for deletion.
local current_buffer = vim.api.nvim_get_current_buf()
for _, buffer in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_valid(buffer) and buffer ~= current_buffer then
vim.api.nvim_buf_delete(buffer, { force = true })
end
end
vim.api.nvim_buf_delete(current_buffer, { force = true })

-- Set the active session filename.
utils.active_session_filename = filename

local swapfile = vim.o.swapfile
vim.o.swapfile = false
vim.api.nvim_exec_autocmds('User', { pattern = 'SessionLoadPre' })
vim.api.nvim_command('silent source ' .. filename)
if not config.resession_backend then
-- Delete all buffers first except the current one to avoid entering buffers scheduled for deletion.
local current_buffer = vim.api.nvim_get_current_buf()
for _, buffer in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_valid(buffer) and buffer ~= current_buffer then
vim.api.nvim_buf_delete(buffer, { force = true })
end
end
vim.api.nvim_buf_delete(current_buffer, { force = true })

local swapfile = vim.o.swapfile
vim.o.swapfile = false
vim.api.nvim_command('silent source ' .. filename)
vim.o.swapfile = swapfile
else
require('resession').load(config.session_filename_to_dir(filename).filename, {
silence_errors = true,
dir = vim.fn.fnamemodify(config.sessions_dir.filename, ':t'),
})
end
vim.api.nvim_exec_autocmds('User', { pattern = 'SessionLoadPost' })
vim.o.swapfile = swapfile
end

---@param filename string
Expand All @@ -75,9 +83,12 @@ function utils.save_session(filename)
end

-- Remove all non-file and utility buffers because they cannot be saved.
for _, buffer in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_valid(buffer) and not utils.is_restorable(buffer) then
vim.api.nvim_buf_delete(buffer, { force = true })
--NOTE: handle by resession if using it :
if not config.resession_backend then
for _, buffer in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_valid(buffer) and not utils.is_restorable(buffer) then
vim.api.nvim_buf_delete(buffer, { force = true })
end
end
end

Expand All @@ -90,7 +101,14 @@ function utils.save_session(filename)
utils.active_session_filename = filename

vim.api.nvim_exec_autocmds('User', { pattern = 'SessionSavePre' })
vim.api.nvim_command('mksession! ' .. filename)
if not config.resession_backend then
vim.api.nvim_command('mksession! ' .. filename)
else
require('resession').save(config.session_filename_to_dir(filename).filename, {
notify = false,
dir = vim.fn.fnamemodify(config.sessions_dir.filename, ':t'),
})
end
vim.api.nvim_exec_autocmds('User', { pattern = 'SessionSavePost' })
end

Expand All @@ -112,10 +130,11 @@ function utils.get_sessions(opts)
-- Add all but the active session to the list.
if config.load_include_current or session_filename ~= utils.active_session_filename then
local dir = config.session_filename_to_dir(session_filename)
if dir:is_dir() then
table.insert(sessions, { timestamp = vim.fn.getftime(session_filename), filename = session_filename, dir = dir })
else
Path:new(session_filename):rm()
if not config.resession_backend or session_filename:sub(-4) == 'json' then
-- Skip .vim files when using resession backend.
if dir:is_dir() then
table.insert(sessions, { timestamp = vim.fn.getftime(session_filename), filename = session_filename, dir = dir })
end
end
end
end
Expand Down Expand Up @@ -173,13 +192,17 @@ end
---@return boolean
function utils.exists_in_session()
local cwd = vim.uv.cwd()
if cwd == nil then
return false
end
return config.dir_to_session_filename(cwd).filename == utils.active_session_filename
end

---@return boolean
function utils.is_dir_in_ignore_list()
local cwd = vim.uv.cwd()
-- Use `fnamemodify` to allow paths like `~/.config`.
---@diagnostic disable-next-line: param-type-mismatch
return vim.tbl_contains(config.autosave_ignore_dirs, cwd) or vim.tbl_contains(config.autosave_ignore_dirs, vim.fn.fnamemodify(cwd, ':~'))
end

Expand Down
Loading