stevearc / resession.nvim

A replacement for mksession with a better API
MIT License
233 stars 19 forks source link
neovim neovim-plugin nvim nvim-plugin

resession.nvim

A replacement for :mksession with a better API


Requirements

Installation

resession supports all the usual plugin managers

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

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

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 = {},
  },
})

API

setup(config)

setup(config) \ Initialize resession with configuration options

Param Type Desc
config table

load_extension(name, opts)

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

Param Type Desc
name string Name of the extension
opts table Configuration options for extension

get_current()

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

Returns:

Type Desc
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

Param Type Desc
opts nil\|resession.ListOpts
dir nil\|string Name of directory to list (overrides config.dir)

delete(name, opts)

delete(name, opts) \ Delete a saved session

Param Type Desc
name nil\|string If not provided, prompt for session to delete
opts nil\|resession.DeleteOpts
dir nil\|string Name of directory to delete from (overrides config.dir)

save(name, opts)

save(name, opts) \ Save a session to disk

Param Type Desc
name nil\|string
opts nil\|resession.SaveOpts
attach nil\|boolean Stay attached to session after saving (default true)
notify nil\|boolean Notify on success
dir nil\|string Name of directory to save to (overrides config.dir)

save_tab(name, opts)

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

Param Type Desc
name nil\|string If not provided, will prompt user for session name
opts nil\|resession.SaveOpts
attach nil\|boolean Stay attached to session after saving (default true)
notify nil\|boolean Notify on success
dir nil\|string Name of directory to save to (overrides config.dir)

save_all(opts)

save_all(opts) \ Save all current sessions to disk

Param Type Desc
opts nil\|resession.SaveAllOpts
notify nil\|boolean Notify on success

load(name, opts)

load(name, opts) \ Load a session

Param Type Desc
name nil\|string
opts nil\|resession.LoadOpts
attach nil\|boolean Stay attached to session after loading (default true)
reset nil\|boolean\|"auto" Close everything before loading the session (default "auto")
silence_errors nil\|boolean Don't error when trying to load a missing session
dir nil\|string Name of directory to load from (overrides config.dir)

Note:

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

add_hook(name, callback)

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

Param Type Desc
name "pre_save"\|"post_save"\|"pre_load"\|"post_load"
callback fun(...: any)

remove_hook(name, callback)

remove_hook(name, callback) \ Remove a hook callback

Param Type Desc
name "pre_save"\|"post_save"\|"pre_load"\|"post_load"
callback fun(...: any)

default_buf_filter(bufnr)

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

Param Type Desc
bufnr integer

is_loading()

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

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.