Home

Awesome

resession.nvim

A replacement for :mksession with a better API


<!-- TOC --> <!-- /TOC -->

Requirements

Installation

resession supports all the usual plugin managers

<details> <summary>lazy.nvim</summary>
{
  'stevearc/resession.nvim',
  opts = {},
}
</details> <details> <summary>Packer</summary>
require("packer").startup(function()
  use({
    "stevearc/resession.nvim",
    config = function()
      require("resession").setup()
    end,
  })
end)
</details> <details> <summary>Paq</summary>
require("paq")({
  { "stevearc/resession.nvim" },
})
</details> <details> <summary>Neovim native package</summary>
git clone --depth=1 https://github.com/stevearc/resession.nvim.git \
  "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/pack/resession/start/resession.nvim
</details>

Quick start

local resession = require("resession")
resession.setup()
-- Resession does NOTHING automagically, so we have to set up some keymaps
vim.keymap.set("n", "<leader>ss", resession.save)
vim.keymap.set("n", "<leader>sl", resession.load)
vim.keymap.set("n", "<leader>sd", resession.delete)

Now you can use <leader>ss to save a session. When you want to load a session, use <leader>sl.

Guides

Automatically save a session when you exit Neovim

vim.api.nvim_create_autocmd("VimLeavePre", {
  callback = function()
    -- Always save a special session named "last"
    resession.save("last")
  end,
})

Periodically save the current session

When you are attached to a session (have saved or loaded a session), use this config to periodically re-save that session in the background.

require("resession").setup({
  autosave = {
    enabled = true,
    interval = 60,
    notify = true,
  },
})

Create one session per directory

Load a dir-specific session when you open Neovim, save it when you exit.

vim.api.nvim_create_autocmd("VimEnter", {
  callback = function()
    -- Only load the session if nvim was started with no args
    if vim.fn.argc(-1) == 0 then
      -- Save these to a different directory, so our manual sessions don't get polluted
      resession.load(vim.fn.getcwd(), { dir = "dirsession", silence_errors = true })
    end
  end,
  nested = true,
})
vim.api.nvim_create_autocmd("VimLeavePre", {
  callback = function()
    resession.save(vim.fn.getcwd(), { dir = "dirsession", notify = false })
  end,
})

Create one session per git branch

Same as above, but have a separate session for each git branch in a directory.

local function get_session_name()
  local name = vim.fn.getcwd()
  local branch = vim.trim(vim.fn.system("git branch --show-current"))
  if vim.v.shell_error == 0 then
    return name .. branch
  else
    return name
  end
end
vim.api.nvim_create_autocmd("VimEnter", {
  callback = function()
    -- Only load the session if nvim was started with no args
    if vim.fn.argc(-1) == 0 then
      resession.load(get_session_name(), { dir = "dirsession", silence_errors = true })
    end
  end,
})
vim.api.nvim_create_autocmd("VimLeavePre", {
  callback = function()
    resession.save(get_session_name(), { dir = "dirsession", notify = false })
  end,
})

Use tab-scoped sessions

When saving a session, only save the current tab

-- Bind `save_tab` instead of `save`
vim.keymap.set("n", "<leader>ss", resession.save_tab)
vim.keymap.set("n", "<leader>sl", resession.load)
vim.keymap.set("n", "<leader>sd", resession.delete)

This will save only the current tabpage layout, but will save all of the open buffers. You can provide a filter to exclude buffers. For example, if you are using :tcd to have tabs open for different directories, this will only save buffers in the current tabpage directory:

require("resession").setup({
  tab_buf_filter = function(tabpage, bufnr)
    local dir = vim.fn.getcwd(-1, vim.api.nvim_tabpage_get_number(tabpage))
    -- ensure dir has trailing /
    dir = dir:sub(-1) ~= "/" and dir .. "/" or dir
    return vim.startswith(vim.api.nvim_buf_get_name(bufnr), dir)
  end,
})

Saving custom data with an extension

To create an extension, create a file in your runtimepath at lua/resession/extensions/myplugin.lua. Add the following contents:

local M = {}

---Get the saved data for this extension
---@param opts resession.Extension.OnSaveOpts Information about the session being saved
---@return any
M.on_save = function(opts)
  return {}
end

---Restore the extension state
---@param data The value returned from on_save
M.on_pre_load = function(data)
  -- This is run before the buffers, windows, and tabs are restored
end

---Restore the extension state
---@param data The value returned from on_save
M.on_post_load = function(data)
  -- This is run after the buffers, windows, and tabs are restored
end

---Called when resession gets configured
---This function is optional
---@param data table The configuration data passed in the config
M.config = function(data)
  --
end

---Check if a window is supported by this extension
---This function is optional, but if provided save_win and load_win must
---also be present.
---@param winid integer
---@param bufnr integer
---@return boolean
M.is_win_supported = function(winid, bufnr)
  return true
end

---Save data for a window
---@param winid integer
---@return any
M.save_win = function(winid)
  -- This is used to save the data for a specific window that contains a non-file buffer (e.g. a filetree).
  return {}
end

---Called with the data from save_win
---@param winid integer
---@param config any
---@return integer|nil If the original window has been replaced, return the new ID that should replace it
M.load_win = function(winid, config)
  -- Restore the window from the config
end

return M

Then to activate it, users can add the extension to their call to setup:

require("resession").setup({
  extensions = {
    myplugin = {
      -- these args will get passed in to M.config()
    },
  },
})

For tab-scoped sessions, the on_save and on_load methods of extensions will be disabled by default. There is a special config argument always available that can override this:

require("resession").setup({
  extensions = {
    myplugin = {
      enable_in_tab = true,
    },
  },
})

Refer to the quickfix extension for a complete example.

Setup options

<!-- Setup -->
require("resession").setup({
  -- Options for automatically saving sessions on a timer
  autosave = {
    enabled = false,
    -- How often to save (in seconds)
    interval = 60,
    -- Notify when autosaved
    notify = true,
  },
  -- Save and restore these options
  options = {
    "binary",
    "bufhidden",
    "buflisted",
    "cmdheight",
    "diff",
    "filetype",
    "modifiable",
    "previewwindow",
    "readonly",
    "scrollbind",
    "winfixheight",
    "winfixwidth",
  },
  -- Custom logic for determining if the buffer should be included
  buf_filter = require("resession").default_buf_filter,
  -- Custom logic for determining if a buffer should be included in a tab-scoped session
  tab_buf_filter = function(tabpage, bufnr)
    return true
  end,
  -- The name of the directory to store sessions in
  dir = "session",
  -- Show more detail about the sessions when selecting one to load.
  -- Disable if it causes lag.
  load_detail = true,
  -- List order ["modification_time", "creation_time", "filename"]
  load_order = "modification_time",
  -- Configuration for extensions
  extensions = {
    quickfix = {},
  },
})
<!-- /Setup -->

API

<!-- API -->

setup(config)

setup(config)
Initialize resession with configuration options

ParamTypeDesc
configtable

load_extension(name, opts)

load_extension(name, opts)
Load an extension some time after calling setup()

ParamTypeDesc
namestringName of the extension
optstableConfiguration options for extension

get_current()

get_current(): string
Get the name of the current session

Returns:

TypeDesc
string?

get_current_session_info()

get_current_session_info(): nil|resession.SessionInfo
Get information about the current session

detach()

detach()
Detach from the current session

list(opts)

list(opts): string[]
List all available saved sessions

ParamTypeDesc
optsnil|resession.ListOpts
dirnil|stringName of directory to list (overrides config.dir)

delete(name, opts)

delete(name, opts)
Delete a saved session

ParamTypeDesc
namenil|stringIf not provided, prompt for session to delete
optsnil|resession.DeleteOpts
dirnil|stringName of directory to delete from (overrides config.dir)

save(name, opts)

save(name, opts)
Save a session to disk

ParamTypeDesc
namenil|string
optsnil|resession.SaveOpts
attachnil|booleanStay attached to session after saving (default true)
notifynil|booleanNotify on success
dirnil|stringName of directory to save to (overrides config.dir)

save_tab(name, opts)

save_tab(name, opts)
Save a tab-scoped session

ParamTypeDesc
namenil|stringIf not provided, will prompt user for session name
optsnil|resession.SaveOpts
attachnil|booleanStay attached to session after saving (default true)
notifynil|booleanNotify on success
dirnil|stringName of directory to save to (overrides config.dir)

save_all(opts)

save_all(opts)
Save all current sessions to disk

ParamTypeDesc
optsnil|resession.SaveAllOpts
notifynil|booleanNotify on success

load(name, opts)

load(name, opts)
Load a session

ParamTypeDesc
namenil|string
optsnil|resession.LoadOpts
attachnil|booleanStay attached to session after loading (default true)
resetnil|boolean|"auto"Close everything before loading the session (default "auto")
silence_errorsnil|booleanDon't error when trying to load a missing session
dirnil|stringName of directory to load from (overrides config.dir)

Note:

<pre> The default value of `reset = "auto"` will reset when loading a normal session, but _not_ when loading a tab-scoped session. </pre>

add_hook(name, callback)

add_hook(name, callback)
Add a callback that runs at a specific time

ParamTypeDesc
name"pre_save"|"post_save"|"pre_load"|"post_load"
callbackfun(...: any)

remove_hook(name, callback)

remove_hook(name, callback)
Remove a hook callback

ParamTypeDesc
name"pre_save"|"post_save"|"pre_load"|"post_load"
callbackfun(...: any)

default_buf_filter(bufnr)

default_buf_filter(bufnr): boolean
The default config.buf_filter (takes all buflisted files with "", "acwrite", or "help" buftype)

ParamTypeDesc
bufnrinteger

is_loading()

is_loading(): boolean
Returns true if a session is currently being loaded

<!-- /API -->

Extensions

FAQ

Q: Why another session plugin?

A: All the other plugins use :mksession under the hood

Q: Why don't you want to use :mksession?

A: While it's amazing that this feature is built-in to vim, and it does an impressively good job for most situations, it is very difficult to customize. If :help sessionoptions covers your use case, then you're golden. If you want anything else, you're out of luck.