Skip to content

Add dump/restore commands to save and recreate session layouts - #5585

Open
asRizvi888 wants to merge 4 commits into
tmux:masterfrom
asRizvi888:dump-restore-layout
Open

asRizvi888 wants to merge 4 commits into
tmux:masterfrom
asRizvi888:dump-restore-layout

Conversation

@asRizvi888

Copy link
Copy Markdown

Summary

  • Adds dump [-f path] to write the layout of every session (sessions,
    windows, panes, splits, active pane/window, and each pane's current
    working directory) to a text file as a sequence of tmux commands.
  • Adds restore [-f path] to recreate that layout by sourcing the file.
    This does not relaunch whatever process was previously running in each
    pane - only the shape of the layout and where each pane's shell starts.
  • Default file location is derived from the directory of the last-loaded
    config file (falling back to $HOME), named .tmux.dump.
  • dump records each session's base-index and pins it before emitting
    indexed commands, so restore places windows correctly even when the
    restoring environment's config sets a different base-index, then
    restores the prior global value afterward.
  • Pane working directories are captured live (via the same mechanism
    as the pane_current_path format variable), not the pane's original
    spawn directory, so restore reflects where you actually left off.

Test plan

  • Dump/restore round-tripped across sessions with multiple windows,
    splits, and custom layouts; verified pane cwds match exactly.
  • Verified restore works correctly when the restoring environment's
    base-index differs from the dump-time value.
  • make (clean build, no new warnings)

@nicm

nicm commented Sep 9, 2026

Copy link
Copy Markdown
Member

Thanks - this is a reasonable start but I think:

  • It should contain a lot more and the intent is for this to use JSON now that we have a parser.
  • Output and input should go via the file.c API like load-buffer/save-buffer.
  • The commands should have more tmux-typical names.

Also can you please put your real name in the copyright header.

- dump previously wrote wp->cwd (a pane's spawn-time directory,
  never updated) instead of its live current directory, so restore
  always recreated panes wherever the shell was originally launched
  rather than where the user had since cd'd to. Now queries the
  live cwd via osdep_get_cwd(wp->fd), same as pane_current_path.
- dump now pins base-index to the recorded value per session before
  emitting indexed commands, and restores the prior global value at
  the end, so restore works correctly even when the restoring
  environment's config sets a different base-index than dump time.
- renamed the default dump file from .tmux-dump to .tmux.dump.
Address review feedback from nicm on the original dump/restore PR:

- Renamed dump -> save-layout and restore -> load-layout to match
  tmux's existing verb-noun command naming (mirrors save-buffer/
  load-buffer).
- The layout file is now JSON (sessions -> windows -> panes), using
  the new json.c parser to read it back, instead of a hand-rolled
  plain-text script. Each window's pane arrangement is stored as the
  same JSON layout tree produced by layout_dump(), so splits,
  floating panes and z-index all round-trip. save-layout writes
  session/window/pane names, indexes, the active window and active
  pane, and each pane's live working directory.
- I/O now goes through file.c (file_write/file_read), the same API
  used by save-buffer/load-buffer, instead of fopen/fwrite directly.
  This also means a path of "-" reads from stdin / writes to stdout,
  consistent with the buffer commands.
- load-layout parses the JSON with json_parse()/json_find_*, builds
  an in-memory command script from it (still using the base-index
  pin/restore trick to place each session's first window at the
  correct recorded index regardless of the restoring server's own
  base-index), then runs it with load_cfg_from_buffer() rather than
  load_cfg() on a path.
- Default file renamed from .tmux.dump to .tmux.layout.
- Updated the manual page entries and moved them to their correct
  alphabetical position under save-layout/load-layout.
- Copyright headers now use my real name.
@nicm

nicm commented Sep 9, 2026

Copy link
Copy Markdown
Member

I don't think you got the point...

@nicm nicm moved this from Not Started to Waiting in Open Issues & PRs Sep 11, 2026
@nicm

nicm commented Sep 21, 2026

Copy link
Copy Markdown
Member

I think this should be entirely driven by JSON, it should not generate tmux commands.

Could you maybe write up how you see this working? All the stuff that would be saved, restored, how the JSON would look, etc. So we can agree the design before writing the code.

@asRizvi888

Copy link
Copy Markdown
Author

Thanks for the pointer - I think I understand now. Here's how I'd like this to work, before I write any more code:

Scope (v1): for every session - name, base-index, current window; for every window - index, name, layout tree (splits/sizes/active pane, the same JSON the new layout format already produces); for every pane - index, live working directory. Not included: window/pane options, session environment, pane titles, zoomed state, or relaunching whatever was running in each pane. I'd rather ship this minimal version first and add any of that afterwards if you want it - let me know if you'd rather see more of that up front.

JSON shape:

{
  "version": 1,
  "sessions": [
    {
      "name": "work",
      "base_index": 0,
      "current_window": 1,
      "windows": [
        {
          "index": 0,
          "name": "zsh",
          "active_pane": 0,
          "layout": { "V": 2, "L": { "t": "p", "w": 80, "h": 24, "x": 0, "y": 0, "a": true, "i": 0, "I": "%0" } },
          "panes": [ { "index": 0, "cwd": "/home/user" } ]
        }
      ]
    }
  ]
}

layout is exactly what layout_dump() already emits for a window, embedded directly rather than escaped into a string.

save-layout: unchanged from the current code - walks the live session/window/pane trees and writes the JSON above via file_write().

load-layout: this is what I'll rework. Right now it parses the JSON and then generates a script of tmux commands (new-session, new-window, select-layout, ...) and runs that through load_cfg_from_buffer(). I understand now that's not what you want - instead it should read the file via file_read(), parse it with json_parse(), and for each session/window/pane call the internal creation functions directly: session_create() for the session, spawn_window() (the same primitive new-session/new-window already use internally, with sc.idx set to the recorded index so each window lands at the right place without needing any base-index tricks) for each window's first pane, and layout_parse() on the embedded layout JSON to build out the rest of that window's panes and geometry. No cmd_parse/load_cfg_from_buffer anywhere in the restore path.

Let me know if this matches what you had in mind, or if I'm still off - happy to adjust before writing the code.

@nicm

nicm commented Sep 24, 2026

Copy link
Copy Markdown
Member

OK I think this is fine but I'm not sure about base_index, what do you need it for? If you are saving the options you should save them all probably (or at least the selection you want to save for now) in an options section - but mostly a session will use the global option here, and I assume we are not saving that.

Address nicm's PR feedback:

- load-layout no longer parses the JSON and generates a script of
  tmux commands to run through load_cfg_from_buffer(). It now walks
  the parsed JSON directly and calls the same internal functions
  new-session/new-window/split-window/select-layout use: session_create()
  for each session, spawn_window() for each window's first pane (with
  sc.idx set to the recorded index, so windows land at the right place
  without any base-index tricks), layout_split_pane()+spawn_pane() to
  get the remaining panes in place, and layout_parse() on the embedded
  layout JSON to get the exact recorded geometry and active pane.
- Dropped base_index from the saved JSON - it was only ever needed to
  place a session's first window correctly when generating new-session
  text, which no longer happens. base-index is a session option, not
  layout data, and most sessions just inherit the global default
  anyway.
- Dropped active_pane from the per-window JSON - it was redundant with
  the "a":true flag already present on the active pane's cell inside
  the layout JSON tree, which layout_parse() already applies.

Verified end to end, including restoring a layout saved under
base-index 0 into a session whose config sets base-index 1: windows
still land at their recorded indexes, cwds and split geometry match,
and the correct pane is active in each window.
@asRizvi888

Copy link
Copy Markdown
Author

Pushed an update addressing this. load-layout no longer generates any tmux commands - it walks the parsed JSON directly and calls session_create(), spawn_window() (with sc.idx set to the recorded index, so windows land correctly with no base-index tricks needed), layout_split_pane()/spawn_pane() for the remaining panes, and layout_parse() on the embedded layout JSON for the exact geometry and active pane.

On base_index specifically: it's gone from the saved JSON. It was only ever needed to place a session's first window correctly when generating new-session text - since spawn_window() can place any window (including the first) at an explicit recorded index directly, it's not needed at all anymore. I also dropped active_pane from the per-window JSON since it was redundant with the "a":true flag already inside the layout tree, which layout_parse() applies on its own.

Verified end to end, including restoring a layout saved under base-index 0 into a session whose config sets base-index 1 - windows still land at their recorded indexes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Work

Development

Successfully merging this pull request may close these issues.

2 participants