sessions.nvim · Files & navigation · vimdoc

:help sessions

Branch- and project-aware Neovim sessions

doc/sessions.txt — rendered from the plugin's own vimdoc

*sessions.txt*   Branch- and project-aware Neovim sessions

                                                                *sessions.nvim*

Author:   Stefan Bartl
Homepage: https://github.com/stefanbartl/sessions.nvim

CONTENTS *sessions-contents*

  1. Introduction ................. |sessions-introduction|
  2. Requirements ................. |sessions-requirements|
  3. Setup ........................ |sessions-setup|
  4. Quick Start .................. |sessions-quick-start|
  5. Configuration ................ |sessions-config|
  6. Commands ..................... |sessions-commands|
  7. Keymaps ...................... |sessions-keymaps|
  8. Public API ................... |sessions-api|
  9. Session naming ............... |sessions-naming|
 10. Metadata ..................... |sessions-metadata|
 11. Git integration .............. |sessions-git|
 12. Health check ................ |sessions-health|

1. INTRODUCTION *sessions-introduction*

sessions.nvim saves and restores Neovim sessions using only built-in Neovim
primitives (|:mksession| / |:source|). Key features:

  - Branch-aware naming: automatically appends the current git branch to the
    session name, so switching branches restores a different workspace.

  - Project-aware naming: detects the project root from markers like .git
    and prefixes the session name with the project basename.

  - Session metadata: writes a companion JSON file with the save timestamp,
    working directory, branch, and buffer list for statusline/picker use.

  - Clean save: blacklisted buftypes/filetypes/paths are wiped before
    |:mksession| so they are never serialised.

  - E445 fix: modified buffers are hidden (not discarded) before loading a
    session to avoid the "cannot close, buffer modified" error.

2. REQUIREMENTS *sessions-requirements*

  - Neovim 0.9+
  - lib.nvim — REQUIRED: the |:Session|/|:LastSession| commands are built on
    lib.nvim.bindings.usercmd.composer. lib.nvim.notify, lib.nvim.bindings.keymap, and
    lib.nvim.git stay soft-guarded (used when available, native fallback
    otherwise).

3. SETUP *sessions-setup*

Minimal setup:
  require("sessions").setup()
With lazy.nvim (lazy-loaded on command use):
  {
    "stefanbartl/sessions.nvim",
    dependencies = { "stefanbartl/lib.nvim" },
    cmd = { "Session", "LastSession" },
    opts = {},
  }
With lazy.nvim (loaded at VimEnter, recommended for autoload/autosave):
  {
    "stefanbartl/sessions.nvim",
    dependencies = { "stefanbartl/lib.nvim" },
    event = "VimEnter",
    opts = {},
  }
With lazy.nvim (eager, needed for nvim +LastSession / nvim '+Session load'
command-line args):
  {
    "stefanbartl/sessions.nvim",
    dependencies = { "stefanbartl/lib.nvim" },
    lazy = false,
    opts = {},
  }
Note: Command-line arguments like nvim +LastSession execute **before**
lazy-loading triggers, so they require lazy = false in the plugin spec.
For all other workflows, event = "VimEnter" or cmd = { ... } is
preferred. lib.nvim is now a REQUIRED dependency (the |:Session| /
|:LastSession| commands are built on lib.nvim.bindings.usercmd.composer).

4. QUICK START *sessions-quick-start*

Save a session (either via :Session save or autosave on exit):
  :Session save
  :quit
Restore the session:
  # Wherever you left off (project + branch aware) -- no quoting, own command
  nvim +LastSession

  # The same resolution, spelled as a :Session subcommand -- needs quoting
  nvim '+Session load'

  # Explicit session name -- needs quoting
  nvim '+Session load myntest'
  nvim '+Session load myapp_feature-login'
:Session is one command with subcommands, built via
lib.nvim.bindings.usercmd.composer. A bare Neovim CLI +cmd flag is a single shell
word, so any invocation containing a space needs to be quoted as one shell
argument. |:LastSession| is a plain, separate, single-word command
specifically so the single most common case (restore wherever you left
off) never needs quoting -- it resolves exactly like a bare
|:Session-load|, not a hardcoded name (see |sessions-naming|).

Common workflow — branch-aware sessions:
  # Work on main branch (auto-named: myapp_main)
  git checkout main
  nvim '+Session load'         # loads myapp_main

  # Switch to feature branch (auto-named: myapp_feature-auth)
  git checkout feature-auth
  nvim '+Session load'         # loads myapp_feature-auth (different workspace)
**Important:** To use nvim +LastSession / nvim '+Session load'
(command-line arguments), your plugin spec must have lazy = false. See
|sessions-setup| for details.

5. CONFIGURATION *sessions-config*

Default values (pass to setup(opts)):
  {
    -- Absolute path for session storage (user-local by default).
    root = vim.fn.stdpath("data") .. "/sessions",

    -- Fallback session name when auto-resolve yields nothing.
    default_name = "last",

    -- Append current git branch to the auto-resolved name.
    branch_aware = true,

    -- Prefix auto-resolved name with the detected project root basename.
    project_aware = true,

    -- Files searched upward from cwd to detect a project root.
    project_markers = {
      ".git", "pyproject.toml", "package.json",
      "Makefile", "Cargo.toml", "go.mod",
    },

    -- Passed to vim.opt.sessionoptions before every save/load.
    sessionoptions = "buffers,curdir,tabpages,winsize,help,folds",

    -- Rewrite the saved cwd to a portable placeholder, re-anchored to cwd
    -- on load. See |sessions-portability| / docs/portability.md.
    relative_paths = false,

    -- Old-root -> new-root path prefixes translated when loading, for
    -- cross-OS/cross-machine sync. See docs/portability.md.
    root_remap = {},

    -- Auto-load the contextual session when Neovim starts without file args.
    -- Set to "ask" to show a floating y/n prompt before loading instead of
    -- loading silently.
    autoload = false,

    -- Auto-save on VimLeavePre (false = disabled).
    autosave = true,

    -- Autosave target, used when autosave = true:
    --   true   -- resolve like a bare :Session save (branch/project-aware
    --             when configured), so different projects don't clobber
    --             each other's autosave.
    --   "name" -- pin autosave to this one fixed name regardless of project.
    --   false  -- no autosave despite autosave = true.
    autosave_name = true,

    -- Write a .{name}.json companion file next to each .vim session.
    metadata = true,

    -- Callbacks invoked after save/load (errors swallowed via pcall).
    hooks = {
      on_save = nil,  -- fun(name: string, path: string)
      on_load = nil,  -- fun(name: string, path: string)
    },

    -- Buffers matching these criteria are wiped before :mksession.
    blacklist = {
      buftypes  = { "quickfix", "nofile", "prompt" },
      filetypes = { "gitcommit", "gitrebase" },
      paths     = { "/tmp/", "/private/tmp/" },
      -- %TEMP% is automatically added on Windows.
    },

    -- Normal-mode keymaps. Disabled by default (keymaps = false).
    -- Set to a table to enable specific keymaps.
    keymaps = false,
    -- or: { save = "<leader>ssa", load = "<leader>slo", ... }

    -- Register a which-key group label for the keymap prefix, if
    -- which-key is installed and at least one keymap is configured.
    which_key = { enable = true },
  }

6. COMMANDS *sessions-commands*

One command, :Session <subcommand> (built via lib.nvim.bindings.usercmd.composer,
with <Tab> completion — session-name args complete dynamically from the
current saved-session list), plus a standalone |:LastSession| convenience
command.
                                                                  *:Session*
:Session save [name]                                        *:Session-save*
    Save the current session. If [name] is omitted the name is auto-resolved
    from project root and/or git branch (see |sessions-naming|).

:Session save-timestamp                            *:Session-save-timestamp*
    Save a snapshot with a timestamp suffix (e.g. sess-20251231-120000).
    Useful for creating restore points before a risky refactor.

:Session load [name]                                        *:Session-load*
    Load a session. [name] supports tab completion from saved sessions. If
    [name] is omitted, the current project/branch's own session is
    preferred when it exists (the same auto-resolution |:Session-save|
    uses), else the remembered last-loaded/saved session, else
    default_name -- see |sessions-naming|.

:Session delete <name>                                    *:Session-delete*
    Permanently delete a session file (and its metadata companion).

:Session rename <old> <new>                                *:Session-rename*
    Rename a session, including its metadata file.

:Session list                                                *:Session-list*
    Print all saved sessions, annotated with the active session (*), save
    timestamp, and branch name from metadata when available.

:Session current                                          *:Session-current*
    Print the name of the currently active session, or report that none is
    active.

:Session toggle-track [name]                        *:Session-toggle-track*
    Toggle git update-index --skip-worktree on the named session file.
    Useful when |sessions-config| root lives inside a git-tracked config
    directory: mark transient sessions as skip-worktree so they are not
    committed, but keep permanent sessions tracked.

    If [name] is omitted, the currently active session (or default_name) is
    used. The root directory must be inside a git repository.

                                                              *:LastSession*
:LastSession
    Load wherever you left off -- the same resolution as a bare
    |:Session-load| (no [name]): the current project/branch's own session
    first, else the remembered last-loaded/saved one, else default_name
    (see |sessions-naming|). A plain, separate, single-word command
    specifically so nvim +LastSession works without CLI-arg quoting — the
    general nvim '+Session load ...' form needs quoting since it is
    multiple shell words.

7. KEYMAPS *sessions-keymaps*

Keymaps are **disabled by default**. Enable them in setup:
  require("sessions").setup({
    keymaps = {
      save    = "<leader>ssa",
      load    = "<leader>slo",
      save_ts = "<leader>sst",
      list    = "<leader>sli",
    },
  })
To disable individual keymaps:
  keymaps = {
    save = "<leader>ssa",
    load = false,  -- disabled
    save_ts = "<leader>sst",
    list = "<leader>sli",
  }

8. PUBLIC API *sessions-api*

All functions return explicit booleans plus a result/error string so callers
can react without parsing notification messages.

require("sessions").setup(opts?)
    Configure and activate the plugin (idempotent).

require("sessions").save(name?)                       -> ok, path_or_err
    Save a session. Returns (true, path) on success, (false, err) on failure.

require("sessions").load(name?)                   -> ok, path_or_err, hidden
    Load a session. Third return value lists buffer names that had unsaved
    changes and were hidden (not discarded) to allow the load.

require("sessions").list()                              -> string[]
    Return absolute paths to all stored .vim session files.

require("sessions").delete(name)                        -> ok, err
    Delete a session and its metadata.

require("sessions").rename(old, new)                    -> ok, err
    Rename a session and its metadata.

require("sessions").current()                           -> string|nil
    Return the name of the active session, or nil. Suitable for statusline
    use:
      local sessions = require("sessions")
      -- in your statusline component:
      local name = sessions.current()
      return name and (" " .. name) or ""
require("sessions").metadata(name)                      -> Sessions.Meta|nil
    Return the metadata table for a saved session, or nil if no metadata
    file exists. Metadata structure:
      { saved_at = "2025-12-31T12:00:00Z", cwd = "/path/to/project",
        branch = "feature-branch", buffers = [...] }

9. SESSION NAMING *sessions-naming*

When no explicit name is passed to |:Session-save| or |:Session-load|, the name
is derived from the current context:

  project_aware=true, branch_aware=true (default):
    {project}_{branch}            e.g. myapp_feature-login

  project_aware=true, branch_aware=false:
    {project}                     e.g. myapp

  project_aware=false, branch_aware=true:
    {branch}                      e.g. feature-login

  both false:
    default_name                  e.g. last

  Not inside a git repo / no project marker found:
    Falls back toward default_name.

Characters unsafe for filenames (slashes, spaces, etc.) are replaced with
- or _. Example: feature/login → feature-login, my project →
my-project.

The table above governs |:Session-save| with no [name], and |:Session-load|
when a [name] *is* given. A bare |:Session-load| (no [name]) and autoload
resolve in priority order instead:

  1. The auto-resolved name for the CURRENT project/branch (the table
     above) -- but only when that session's file already exists.
  2. The remembered last-loaded/saved session: .state.json in root,
     one pointer shared by every project -- wins only when (1) has nothing
     to offer (naming is off, or this project has never been saved).
  3. default_name, if neither resolves to an existing file.

(1) is checked first deliberately: preferring the remembered pointer
unconditionally meant opening a *different* project with autoload = true
could silently load whatever session was last touched elsewhere.

10. METADATA *sessions-metadata*

When metadata = true (default), sessions.nvim writes a hidden JSON file
alongside each .vim session file:

  {root}/.{name}.json

Example contents:
  {
    "saved_at": "2025-12-31T12:00:00Z",
    "cwd":      "/home/user/myproject",
    "branch":   "feature/login",
    "buffers":  ["/home/user/myproject/src/main.lua", ...]
  }

The metadata is used by |:Session-list| to display timestamps and branches.
You can read it programmatically via require("sessions").metadata(name).

11. GIT INTEGRATION *sessions-git*

|:Session-toggle-track| is designed for the workflow where root points inside
a git-tracked config directory (e.g. stdpath("config") .. "/sessions").

Scenario: you want to sync named sessions across machines via git but exclude
the last auto-save, because last contains absolute paths that only exist
on one machine.

  1. Set root to a path inside your config repo:
     root = vim.fn.stdpath("config") .. "/sessions"

  2. Commit your named sessions normally.

  3. Run :Session toggle-track last to mark last.vim as skip-worktree.
     The file stays on disk but git ignores local changes to it.

  4. On a different machine, run :Session toggle-track last again to start
     tracking it there too if you want.

12. HEALTH CHECK *sessions-health*

Run:
  :checkhealth sessions
Reports: Neovim version, optional dependency availability, active
configuration, session root status, session count, and command registration.