wez / wezterm

A GPU-accelerated cross-platform terminal emulator and multiplexer written by @wez and implemented in Rust
https://wezfurlong.org/wezterm/
Other
16.72k stars 749 forks source link

Provide a way to save the current layout #3237

Open jason0x43 opened 1 year ago

jason0x43 commented 1 year ago

Is your feature request related to a problem? Please describe.

One of the features I miss most from tmux when using Wezterm's native tabs and panes is the ability to save and later restore the active layout. This is useful when updating the terminal client, when restarting my laptop, or just when I want to close whatever I was doing for work on Friday and restore it on Monday.

This is related to #1949.

Describe the solution you'd like

Wezterm currently provides APIs to initialize a layout at startup. As far as I'm aware, though, there isn't straightforward way to save the current layout. That would be ideal -- a function that could be called, such as from a keybinding or some other event, that would save a snapshot of the domain to a file, and a complementary function that could restore such a snapshot.

Ideally, the snapshot would save at least the layout within each window (tabs and panes), tab and pane titles, and CWD in each pane. The ability to optionally save and restore the scroll buffers of all the panes would also be worthwhile.

I'm less concerned with saving window positions and sizes. Other tools make snapping windows into position easy enough; the worthwhile part would be restoring the general context of whatever I was working on when I saved the layout.

Describe alternatives you've considered

Use a terminal multiplexer than can save layouts, such as tmux with the resurrect plugin.

Additional context

RazaGR commented 1 year ago

I want to add that it would be also great to have the ability to also save workspaces and what it was running

@wez any idea if this will be implemented? Thanks

wez commented 1 year ago

No one is working on this at this time. I have no plans to work on this any time soon. If you search for other issues around saving/restoring state you will discover the set of tricky concerns that need to be considered before attempting to implement this feature.

vvzen commented 1 year ago

+1, since this would be lovely and quite useful!

I have recently switched from iTerm2+tmux to just using wezterm on macOS and it has been a beautiful journey so far. The only missing piece right now is the fact that now I have to shut down my laptop to do an OS update and I'm gonna lose all of my carefully crafted tabs and workspaces, unless I find I way to serialize everything down and restore it later.

When in tmux-land, I was using https://github.com/tmux-plugins/tmux-resurrect .

Even if this is not being planned right now, do you think there could be a way to achieve something similar (hacking it away) by creating the required workspaces and tabs when the gui-startup event fires (https://wezfurlong.org/wezterm/config/lua/gui-events/gui-startup.html) ? I'm asking since I'm still kinda new and if you think it's not worth the effort I'll avoid even going in this rabbit hole :D

Thanks!

wez commented 1 year ago

That's exactly what the gui-startup event is for

jason0x43 commented 1 year ago

If you search for other issues around saving/restoring state you will discover the set of tricky concerns that need to be considered before attempting to implement this feature.

I did look at some existing issues, particularly #256, but I think those are asking for more than I'm looking for. My overall goal is just to do what tux-resurrect does -- save a layout, including scrollback text and CWDs, and then restore that later. I'm not concerned about window positions or sizes, just relative pane sizes and a few other things (tab titles, scrollback text, CWD). This functionality is very useful if a user needs to do something that would disrupt their terminal session, like rebooting or updating Wezterm.

The API already includes most of the necessary functionality -- you can walk through the list of windows, tabs, and panes, and save all that to a file, and create new windows, tabs, and panes. I think the main thing that's missing (or at least that I don't know how to do yet) is a way to save the scrollback buffer.

jorgebef commented 10 months ago

For whoever might be interested, I'm currently preparing just a couple of session management functions that are rather basic but fill my current needs:

local function getTableLn(tbl)
  local getN = 0
  for _ in pairs(tbl) do
    getN = getN + 1
  end
  return getN
end

-- Save JSON data a file
local function save_json(data, json_session_file)
  local f = assert(io.open(json_session_file, "w"))
  -- f:write(wezterm.json_encode(deepcopy(data)))
  -- f:write(table.tostring(data))
  f:write(JSON:encode(data))
  -- f:write("this should be the session info")
  f:close()
end

function M.save_session()
  return action_callback(function()
    -- os.execute([[wezterm cli list --format json > /Users/jbef/.config/wezterm/session2.json]])
    local sessions = {}
    for _, win in ipairs(mux.all_windows()) do
      local workspace = win:get_workspace()
      if sessions[workspace] == nil then
        sessions[workspace] = {}
      end
      wezterm.log_info(win:get_workspace())
      for _, tab in ipairs(win:tabs()) do
        local tabId = tostring(tab:tab_id())
        sessions[workspace][tabId] = {}
        for _, pane in ipairs(tab:panes_with_info()) do
          local paneId = tostring(pane.pane:pane_id())
          sessions[workspace][tabId][paneId] = {
            cwd = tostring(pane.pane:get_current_working_dir()):gsub(".*/Users", "/Users"),
            tty = tostring(pane.pane:get_foreground_process_name()),
          }
        end
      end
    end
    save_json(sessions, file2)
    -- wezterm.log_info(sessions)
  end)
end

function M.restore()
  -- ==========================
  -- check if file exists
  local openFile = io.open(file2, "r")
  if openFile == nil then
    return
  end
  --==========================

  local dump = openFile:read("*a")
  openFile:close()
  ---@type table
  local decoded_dump = JSON:decode(dump)

  -- Loop through Workspaces =====================================
  for workspace, wTable in pairs(decoded_dump) do
    -- initialize Tab counter for each workspace ===============
    local tCount = 0
    local _, _, window = mux.spawn_window({
      workspace = workspace,
      cwd = "~/",
    })
    -- Loop through Tabs ======================================
    for _, tTable in pairs(wTable) do
      local pCount = 0
      -- if getTableLn(wTable) > 1 and wTab:tab_id() ~= window:active_tab():tab_id() then
      if getTableLn(wTable) > 1 and tCount > 0 then
        local tab, pane, _ = window:spawn_tab({})
        tab:activate()
        pane:activate()
        tCount = tCount + 1
      else
        tCount = tCount + 1
      end

      -- initialize Pane counter for each tab ===============
      -- Loop through Panes ======================================
      for _, pTable in pairs(tTable) do
        -- -- Check if it's not the first tab ======================================
        if getTableLn(tTable) > 1 and pCount > 0 then
          window:active_pane():split({ cwd = pTable["cwd"] })
          pCount = pCount + 1
        else
          window:active_pane():send_text("cd " .. pTable["cwd"] .. "\r")
          pCount = pCount + 1
        end
      end
    end
  end
end

The save_session function produces a json that is stored in the provided path, and the restore function takes said json and reads it to produce an initial layout when called on 'gui-startup' as follows:

local session_manager = require("user.session-manager")
...
...
wezterm.on("gui-startup", function()
  session_manager.restore()
end)

The only thing I can't seem to put my finger on is that seemingly randomly, sometimes the initial tab created when calling mux.spawn_window() is placed in the first spot and sometimes not. This coincides with the times that it spawns with the default cwd or when it is called after the spawn_tab command, which I think should never happen provided they are nested for loops.

I have basic lua knowledge, so I might be completely missing something. If anyone has a working solution or a way to improve the above mentioned one, I am very happy to seek alternatives!

Also, thank you Wez for your work, I seem to keep finding more and more utilities for Wezterm and replace many parts of my workflow that were slowing me or the performance down.

danielcopper commented 9 months ago

If anyone is interested i created something very similar. I'm open to collab on this. https://github.com/danielcopper/wezterm-session-manager

Destroy666x commented 6 months ago

Does anyone know a way to make any of these workarounds work automatically with window closing? I see no event for that, so impossible? I guess the closest would be to have a periodic function that saves the session ever few seconds.