Repository navigation
Conversation
…opy of it.
save-state -w writes a window to a file as JSON and load-state -w creates a new window from it with the same name, layout, zoom and pane titles, and nothing running in its panes. Files go through file_write and file_read as with save-buffer and load-buffer, so "-" is stdout or stdin. -t and -d on load-state are as for new-window.
The file is {"version":1,"window":{...}}; later scopes (a session, the server) will hold the same window object. The window has "name", "layout" (the v2 object from layout_dump, the full layout if zoomed), "zoomed" and "panes"; each pane has "title", "cwd", "command", "zoomed" and "float-over-zoom". The loader ignores keys it does not know and treats a missing key as empty or false, since json.c has no empty strings.
- "command" is the pane's argv as a list. cmd_stringify_argv quotes a single argument, so loading its output as one argument would break respawn-pane.
- Names and titles are written decoded from vis(3) and loaded through clean_name, as rename-window and select-pane -T do, so they round trip.
- The working directory is the process's current one, falling back to where the pane started. It is not expanded as a format on load: the spawn context has no item, so a directory named #(...) cannot run a command.
- The panes are created empty (SPAWN_EMPTY) but keep the saved command and directory, so respawn-pane starts the original command where it was.
Everything is checked before anything is created. A new layout_check_string in layout-custom.c checks the layout fits the panes without a window, sharing the size checks with layout_parse. The first pane comes from spawn_window, the others get floating cells, which need no space, and layout_parse then places them by index. A window with a manual size gets the layout's size first. Giving the window a name turns automatic-rename off, as new-window -n does.
json.c gains json_write_string and json_decode_string, and json_parse refuses input over INT_MAX bytes because it keeps offsets in an int.
Not saved: modal panes. Screen contents and options follow.
Each pane gets a "screen" object with "sx" and "sy", "cursor", "history" (oldest first) and "lines". While the alternate screen is on, "alternate" holds the normal screen's lines, which tmux keeps aside as screen_alternate_on leaves them, and "alternate-cursor" holds the cursor to go back to, which is kept even after the alternate screen ends.
A line is {"c":[runs]} with "w":true if wrapped. A run is consecutive cells with the same style: "t" is the text and "a", "f", "b" and "u" the attributes and colours by name, left out when default. Padding after a wide character is implied by its width. A run that does not start where the widths before it put it has its column in "x"; only padding is left out, so the gap is padding again on load. A tab gets its own run with its width in "s". SELECTED and CLEARED are dropped: one is transient and the other draws exactly as the space with the same background that it becomes. NOPALETTE is only used on copy mode's own screen.
Loading splits text into cells with the current width tables and the same rules as screen_write_combine, which now asks a new utf8_combine for its decision so the two cannot drift. Cells written apart that the rules would join start a new run when saved. If a width has changed, the cell is kept and the rest of the run moves along; cells past the edge are dropped, so the grid stays well formed.
The grids are built while the file is read, so a bad screen still fails before anything is created, with the history limit of the target session (keeping the newest lines). After the layout and zoom are applied each pane's grid is swapped in and resized to the pane, which does nothing if the size is the same. The cursor may be past the last column, because the alternate screen is not reflowed when a pane gets narrower; outside the alternate screen it is moved in before reflowing.
A line can hold cells beyond the width after a pane is made narrower in the alternate screen; only those that fit are loaded.
The window and each pane get an "options" list of the options set on that object itself, hooks and user options included: {"name":...,"value":...}, with "index" for an array item and an entry with no index for an array set with no items. Values come from options_to_string, so flags, choices, colours and keys are names.
load-state applies each list to a scratch options tree first, as set-option would, so an unknown option, one of the wrong scope or a bad value fails the load with the option's name before anything is created. The new window's and panes' own options are then replaced by those and options_push_changes is run for each, as set-option does. A window saved with automatic-rename inherited keeps renaming itself; a file without options leaves automatic-rename off as before.
The options are set before the layout is applied, because pane-border-status and pane-scrollbars change pane sizes.
Hooks and formats in options can run commands, so a state file must be trusted like a configuration file; the manual page says so.
So that loading a window loses nothing a later restart needs, the screen object also gets the cursor style and colour ("cursor-style", "cursor-colour"), "modes" by name, the scroll "region", "tabs", the title stack ("titles"), the OSC 7 "path", the cursor and cell saved by DECSC ("saved"), the cell to go back to with "alternate-cursor", hyperlinks ("links", with "l" on runs) and OSC 133 marks ("m" on lines); the pane gets its "palette" (OSC 4, 10 and 11). Each is optional, and all but the modes are left out when they are what a new pane has.
- MODE_SYNC is not saved: it is ended by a timer a new pane does not have.
- The default cursor style, colour and modes are not saved because they come from the cursor-style and cursor-colour options.
- The scroll region and tab stops are set before the screen is resized to the pane, which resets them if the size changes.
- DECSC lives in the input parser, so input.c gains input_get_saved_cursor and input_set_saved_cursor. The saved position can be outside the screen after a resize; DECRC moves it in.
- screen.c gains screen_get_title to read the title stack.
- Hyperlinks are read into a set of their own and the pane's set is replaced with the grid; a run naming a link that is not in the file loads without one.
Not saved: sixel images, the progress bar, and the input parser's current state, which only an in-place restart needs.
Member
|
This looks like the right kind of idea but I have a lot of stuff to get through so I'm not sure when I will be able to have a closer look. I imagine this will be a 4.0 feature not 3.9 in any case. |
Author
|
Makes sense, we can park it or even discard. The most important bits for me were the early patches with support for XDG_RUNTIME_DIR and the bug fixes. These save/load and terminal magic are nice-to-have and not blocking anything. Take your time, let me know if you need any help. Cheers! |
Member
|
OK I will leave it open but I imagine it will not be for a while. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the first step from #5693:
tmux save-state -w - | tmux load-state -w -creates a new window that is the same as the current one, except that nothing is running in its panes. The JSON is meant to grow, with sessions next (-s), then the whole server (-a), and then the in-place restart on top of that, as discussed there.There are four commits, and each builds and works on its own:
-tand-donload-stateare as fornew-window, and-means stdout or stdin as withsave-bufferandload-buffer.A saved window looks like this, shortened:
{"version":1,"window":{"name":"vim","layout":{"V":2,"L":{...}},"options":[...], "panes":[{"title":"host","cwd":"/src","command":[{"arg":"vim"},{"arg":"x.c"}], "options":[],"screen":{"sx":80,"sy":24,"cursor":{"x":3,"y":5}, "modes":[{"name":"cursor"},{"name":"wrap"}],"history":[...], "lines":[{"c":[{"t":"int ","a":"bright"},{"t":"main","f":"red"}]},{}]}}]}}Here is how it works. Unless you see a problem with any of it, I will build sessions on the same basis:
"". Later stages can then add keys without changingversion. A file with a higher version is refused.load-statechecks the whole file before it creates anything. That includes the layout, through a newlayout_check_stringthat shares its size checks withlayout_parse, and the options, which are first applied to a scratch tree. A bad file fails with a message, and no window appears, not even for a moment.new-window -E, but they keep the saved command and directory, sorespawn-panestarts the original command in the original place.cmd_stringify_argvquotes a single argument, so loading its output back as one argument would breakrespawn-pane.clean_nameasrename-windowdoes. The working directory is never expanded as a format: the spawn context has no item, so a directory named#(...)cannot run a command.screen_write_combineinto a newutf8_combinein utf8-combined.c, which both use, so the two cannot drift. The existing combine and UTF-8 tests pass unchanged.load-bufferalready has a-wwith another meaning, which is why they are not next to it in BUFFERS.Not saved yet:
Only the in-place restart needs the input parser's state.
Testing:
save-load-state-window.sh,-screen.sh,-options.shand-terminal.sh. They cover the round trips and save/load/save giving the same JSON, as well as wide and combining characters, cells left misplaced by erasing half of a wide character, the alternate screen, panes resized after saving, and about 70 kinds of invalid file.--enable-sixel, and passes regress. The only failures arecopy-mode-search-count.shandjoin-pane-modal.sh, two new upstream tests that fail the same way on master.