wez / wezterm

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

Buggy muxing / workspaces #1978

Closed Lenbok closed 1 year ago

Lenbok commented 2 years ago

What Operating System(s) are you seeing this problem on?

Linux X11

WezTerm version

wezterm 20220511-182551-566b58a6

Did you try the latest nightly build to see if the issue is better (or worse!) than your current version?

Yes, and I updated the version box above to show the version of the nightly that I tried

Describe the bug

I'm trying to reproduce my tmux workflow, and it seems I'm hitting some bugs. I normally have tmux running on my office workstation, and inside it I have several sessions (one per client/project), and each session has a bunch of (tmux) windows related to it. I can attach to that tmux from any number of graphical terminals, whether they be local to the office, or remote via ssh when I WFH.

With wezterm I'm getting multiple windows opening when I try to connect to an existing ssh domain and/or switch workspaces.

To Reproduce

I axed off any wezterm mux server and clients on my work and home pc, and installed the latest appimage on both.

From a non-wezterm on my home pc I ran wezterm connect noir (thats' an ssh domain for my work pc). It gave me a new GUI with just a default workspace. I then SwitchToWorkspace'd a couple of different workspaces (say A and B, plus the original default) and created some number of tabs in each and did some different lses to differentiate them.

Now back on the non-wezterm on my home pc I ran wezterm connect noir again. It popped up a two new GUIs:

Configuration

local wezterm = require("wezterm");

local hostname = wezterm.hostname();
local irows = 55;
local icols = 191;
if hostname == "noir" then
   irows = 65;
   icols = 191;
end

-- Powerline dividers
local SOLID_RIGHT_ARROW = utf8.char(0xe0b0); -- The  symbol
local RIGHT_ARROW = utf8.char(0xe0b1);       -- The  symbol
local SOLID_LEFT_ARROW = utf8.char(0xe0b2);  -- The  symbol
local LEFT_ARROW = utf8.char(0xe0b3);        -- The  symbol

-- Foreground color for the text across the tabs
local tab_text_fg = "#000000";
-- Color of the empty space in the middle of the status bar
local tab_empty_bg = "#000000";

-- Color palette for the backgrounds of right status tab cells
local tab_segment_colors = {
  "#ffff00",
  "#cccc00",
  "#aaaa00",
  "#777700",
};

-- An alternate palette
-- local tab_text_fg = "#c0c0c0";
-- local tab_segment_colors = {
--   "#b491c8",
--   "#7c5295",
--   "#663a82",
--   "#52307c",
--   "#3c1361",
-- };

wezterm.on("toggle-ligature", function(window, pane)
  local overrides = window:get_config_overrides() or {}
  if not overrides.harfbuzz_features then
    -- If we haven't overridden it yet, then override with ligatures disabled
    overrides.harfbuzz_features =  {"calt=0", "clig=0", "liga=0"}
  else
    -- else we did already, and we should disable out override now
    overrides.harfbuzz_features = nil
  end
  window:set_config_overrides(overrides)
end)

wezterm.on("format-tab-title", function(tab, tabs, panes, config, hover, max_width)
  local edge_background = "#0b0022"
  local background = "#005500"
  local foreground = tab_text_fg

  if tab.is_active then
    background = "#00cc00"
  elseif hover then
    background = "#008800"
  end

  local edge_foreground = background

  -- ensure that the titles plus decorations fit in the available space,
  -- and that we have room for the edges.
  local title = wezterm.truncate_right(tab.active_pane.title, max_width-5)
  local elements = {};
  if tab.tab_index > 0 then
     table.insert(elements, {Background={Color=edge_foreground}});
     table.insert(elements, {Foreground={Color=edge_background}});
     table.insert(elements, {Text=SOLID_RIGHT_ARROW});
  else
     table.insert(elements, {Text=" "});
  end
  table.insert(elements, {Background={Color=background}});
  table.insert(elements, {Foreground={Color=foreground}});
  table.insert(elements, {Text=(tab.tab_index + 1) .. "›" .. title .. " "});
  table.insert(elements, {Background={Color=edge_background}});
  table.insert(elements, {Foreground={Color=edge_foreground}});
  table.insert(elements, {Text=SOLID_RIGHT_ARROW});
  return elements;
end)

wezterm.on("update-right-status", function(window, pane)
  -- Each element holds the text for a cell
  local cells = {};

  -- Show which key table is active in status area
  -- local tablename = window:active_key_table();
  -- table.insert(cells, tablename or "no table");

  -- Figure out the cwd and host of the current pane.
  -- This will pick up the hostname for the remote host if your
  -- shell is using OSC 7 on the remote host.
  local cwd_uri = pane:get_current_working_dir()
  if cwd_uri then
    cwd_uri = cwd_uri:sub(8);
    local slash = cwd_uri:find("/")
    local cwd = ""
    local hostname = ""
    if slash then
      hostname = cwd_uri:sub(1, slash-1)
      -- Remove the domain name portion of the hostname
      local dot = hostname:find("[.]")
      if dot then
        hostname = hostname:sub(1, dot-1)
      end
      -- and extract the cwd from the uri
      cwd = cwd_uri:sub(slash)

      table.insert(cells, cwd);
      table.insert(cells, hostname);
    end
  end

  -- Show which workspace is active in status area
  local workspacename = window:active_workspace();
  table.insert(cells, workspacename);

  -- I like my date/time in this style: "Wed Mar 3 08:14"
  local date = wezterm.strftime("%a %Y-%m-%d %H:%M");
  table.insert(cells, date);

  -- The elements to be formatted
  local elements = {};
  local total_cells = #cells; -- How many cells to format
  local num_cells = 0;        -- How many cells have been formatted

  -- Translate a cell into elements in a "powerline" style << fade
  function push(text, is_last)
    local segment_color_idx = total_cells - num_cells
    if num_cells == 0 then
      table.insert(elements, {Background={Color=tab_empty_bg}})
      table.insert(elements, {Foreground={Color=tab_segment_colors[segment_color_idx]}})
      table.insert(elements, {Text=LEFT_ARROW})
      table.insert(elements, {Text=SOLID_LEFT_ARROW})
    end
    table.insert(elements, {Foreground={Color=tab_text_fg}})
    table.insert(elements, {Background={Color=tab_segment_colors[segment_color_idx]}})
    table.insert(elements, {Text=" "..text.." "})
    if not is_last then
      table.insert(elements, {Foreground={Color=tab_segment_colors[segment_color_idx - 1]}})
      table.insert(elements, {Text=SOLID_LEFT_ARROW})
    end
    num_cells = num_cells + 1
  end

  while #cells > 0 do
    local cell = table.remove(cells, 1)
    push(cell, #cells == 0)
  end

  window:set_right_status(wezterm.format(elements));
end);

return {

   -- default_gui_startup_args = {"connect", "localhost"},
   unix_domains = {
      {
         name = "localhost",
      },
   },

   ssh_domains = {
      -- All we really need is remote_address, other configuration (usernames etc) is in ~/.ssh/config
      {
         name = "noir",
         remote_address = "noir",
      },
      {
         name = "tron",
         remote_address = "tron",
      },
      {
         name = "hardhead",
         remote_address = "hardhead",
      },
   },

   --
   -- Behavior
   --

   check_for_updates = false,
   disable_default_key_bindings = true,
   exit_behavior = "Close",

   enable_tab_bar = true,  -- false when using tmux, true for wezterm multiplexing

   leader = { key="`"}, -- use this when you're not using tmux. Add mods = "CTRL" to disable if using tmux
   keys = {
      -- Send "`" key to the terminal when pressed after LEADER
      { key = "`", mods = "LEADER",       action = wezterm.action{SendString = "`"}},
      { key = "-", mods = "LEADER",       action = wezterm.action{SplitVertical = {domain = "CurrentPaneDomain"}}},
      { key = "\\",mods = "LEADER",       action = wezterm.action{SplitHorizontal = {domain = "CurrentPaneDomain"}}},
      { key = "c", mods = "LEADER",       action = wezterm.action{SpawnTab = "CurrentPaneDomain"}},
      { key = "h", mods = "LEADER",       action = wezterm.action{SplitVertical = {domain = "CurrentPaneDomain", args = {"htop"}}}},
      { key = "l", mods = "LEADER",       action = wezterm.action{EmitEvent = "toggle-ligature"}},
      { key = "\t",mods = "LEADER",       action = "ActivateLastTab"},
      { key = "1", mods = "LEADER",       action = wezterm.action{ActivateTab = 0}},
      { key = "2", mods = "LEADER",       action = wezterm.action{ActivateTab = 1}},
      { key = "3", mods = "LEADER",       action = wezterm.action{ActivateTab = 2}},
      { key = "4", mods = "LEADER",       action = wezterm.action{ActivateTab = 3}},
      { key = "5", mods = "LEADER",       action = wezterm.action{ActivateTab = 4}},
      { key = "6", mods = "LEADER",       action = wezterm.action{ActivateTab = 5}},
      { key = "7", mods = "LEADER",       action = wezterm.action{ActivateTab = 6}},
      { key = "8", mods = "LEADER",       action = wezterm.action{ActivateTab = 7}},
      { key = "9", mods = "LEADER",       action = wezterm.action{ActivateTab = 8}},
      { key = "n", mods = "LEADER",       action = wezterm.action{ActivateTabRelative = 1}},
      { key = "p", mods = "LEADER",       action = wezterm.action{ActivateTabRelative = -1}},
      { key = "<", mods = "LEADER|SHIFT", action = wezterm.action{MoveTabRelative = -1}},
      { key = ">", mods = "LEADER|SHIFT", action = wezterm.action{MoveTabRelative = 1}},

      { key = "z", mods = "LEADER",       action = "TogglePaneZoomState"},

      { key = " ", mods = "LEADER",       action = "ShowLauncher"},
      { key = " ", mods = "LEADER|CTRL",       action = wezterm.action{ShowLauncherArgs = {flags = "FUZZY|DOMAINS|WORKSPACES|TABS|LAUNCH_MENU_ITEMS|KEY_ASSIGNMENTS"}}},
      -- { key = " ", mods = "LEADER",       action = wezterm.action{ShowLauncherArgs = {flags = "FUZZY"}}},

      { key = "&", mods = "LEADER|SHIFT", action = wezterm.action{CloseCurrentTab = {confirm = true}}},
      { key = "x", mods = "LEADER",       action = wezterm.action{CloseCurrentPane = {confirm = true}}},
      { key = "w", mods = "LEADER",       action = "ShowTabNavigator"},

      { key = "s", mods = "LEADER",       action = wezterm.action{ShowLauncherArgs = {title = "Workspaces", flags = "FUZZY|WORKSPACES"}}},
      { key = "D", mods = "LEADER",       action = wezterm.action{SwitchToWorkspace = {name = "@SRV"}}},
      { key = "H", mods = "LEADER",       action = wezterm.action{SwitchToWorkspace = {name = "HOME"}}},
      { key = "L", mods = "LEADER",       action = wezterm.action{SwitchToWorkspace = {name = "LIC"}}},
      { key = "R", mods = "LEADER",       action = wezterm.action{SwitchToWorkspace = {name = "RTG"}}},
      { key = "S", mods = "LEADER",       action = wezterm.action{SwitchToWorkspace = {name = "SAREPTA"}}},
      { key = "T", mods = "LEADER",       action = wezterm.action{SwitchToWorkspace = {name = "TEMPUS"}}},
      { key = "W", mods = "LEADER",       action = wezterm.action{SwitchToWorkspace = {name = "WORK"}}},

      { key = "s", mods = "LEADER|CTRL",  action = wezterm.action{Search = {CaseInSensitiveString=""}}},

      { key = "y", mods = "LEADER|CTRL",   action = wezterm.action{PasteFrom = "PrimarySelection"}},

      { key = "'", mods = "LEADER",       action = "QuickSelect"},
      { key = "'", mods = "LEADER|ALT",   action = "QuickSelect"},

      { key = "[", mods = "LEADER",       action = "ActivateCopyMode"},
   },

   key_tables = {
      -- Emacs-ish bindings in copy mode
      copy_mode = {
         { key = "Escape", mods = "NONE", action = wezterm.action{CopyMode = "Close"}},
         { key = "g", mods = "CTRL", action = wezterm.action{CopyMode = "Close"}},

         { key = "b", mods = "CTRL", action = wezterm.action{CopyMode = "MoveLeft"}},
         { key = "f", mods = "CTRL", action = wezterm.action{CopyMode = "MoveRight"}},
         { key = "p", mods = "CTRL", action = wezterm.action{CopyMode = "MoveUp"}},
         { key = "n", mods = "CTRL", action = wezterm.action{CopyMode = "MoveDown"}},
         { key = "b", mods = "ALT", action = wezterm.action{CopyMode = "MoveBackwardWord"}},
         { key = "f", mods = "ALT", action = wezterm.action{CopyMode = "MoveForwardWord"}},
         { key = "a", mods = "CTRL", action = wezterm.action{CopyMode = "MoveToStartOfLine"}},
         { key = "e", mods = "CTRL", action = wezterm.action{CopyMode = "MoveToEndOfLineContent"}},
         { key = "m", mods = "ALT", action = wezterm.action{CopyMode = "MoveToStartOfLineContent"}},
         { key = "v", mods = "CTRL", action = wezterm.action{CopyMode = "PageDown"}},
         { key = "v", mods = "ALT", action = wezterm.action{CopyMode = "PageUp"}},
         { key = "<", mods = "ALT|SHIFT", action = wezterm.action{CopyMode = "MoveToScrollbackTop"}},
         { key = ">", mods = "ALT|SHIFT", action = wezterm.action{CopyMode = "MoveToScrollbackBottom"}},
         { key = " ", mods = "CTRL", action = wezterm.action{CopyMode = "ToggleSelectionByCell"}},
         { key = "s", mods = "CTRL",  action = wezterm.action{Search = {CaseInSensitiveString=""}}},
         { key = "r", mods = "CTRL",  action = wezterm.action{Search = {CaseInSensitiveString=""}}},
         { key = "w", mods = "ALT", action = wezterm.action{Multiple = {
              wezterm.action{CopyTo = "ClipboardAndPrimarySelection"},
              wezterm.action{CopyMode = "Close"},
                                                           }}
         },
      },
      search_mode = {
         { key = "Escape", mods = "NONE", action = wezterm.action{Multiple = {
              wezterm.action{CopyMode = "ClearPattern"},
              wezterm.action{CopyMode = "Close"},
                                                                 }}
         },
         { key = "g", mods = "CTRL", action = wezterm.action{Multiple = {
              wezterm.action{CopyMode = "ClearPattern"},
              wezterm.action{CopyMode = "Close"},
                                                                 }}
         },
         { key = "Enter", mods = "NONE", action = wezterm.action{CopyMode = "Close"}},
         { key = "s", mods = "CTRL", action = wezterm.action{CopyMode = "NextMatch"}},
         { key = "r", mods = "CTRL", action = wezterm.action{CopyMode = "PriorMatch"}},
         { key = "n", mods = "CTRL", action = wezterm.action{CopyMode = "NextMatch"}},
         { key = "p", mods = "CTRL", action = wezterm.action{CopyMode = "PriorMatch"}},
         { key = "DownArrow", mods = "NONE", action = wezterm.action{CopyMode = "NextMatch"}},
         { key = "UpArrow", mods = "NONE", action = wezterm.action{CopyMode = "PriorMatch"}},
         { key = "PageDown", mods = "NONE", action = wezterm.action{CopyMode = "NextMatchPage"}},
         { key = "PageUp", mods = "NONE", action = wezterm.action{CopyMode = "PriorMatchPage"}},
         --{ key = "r", mods = "CTRL", action = wezterm.action{CopyMode = "CycleMatchType"}},
         { key = "u", mods = "CTRL", action = wezterm.action{CopyMode = "ClearPattern"}},
      },
   },

   enable_kitty_graphics = true,

   --
   -- Appearance
   --
   --font = wezterm.font("JetBrains Mono"),
   font = wezterm.font("Hack FC Ligatured"),
   font_size = 12,

   initial_rows = irows,
   initial_cols = icols,

   tab_bar_at_bottom = true,
   window_padding = { left = 0, right = 0, bottom = 0, top = 0,},
   use_fancy_tab_bar = false,

   -- Appearance of "fancy" tab bar?
   window_frame = {
      inactive_titlebar_bg = "#111111",
      -- active_titlebar_bg = "#33aa55",
      -- inactive_tab_edge = "#ff4455",
   },

   -- Appearance of non-fancy tab bar
   colors = {
      selection_fg = "#cccccc",
      selection_bg = "#444488",

      tab_bar = {
         -- The color of the strip that goes along the top of the window
         -- (does not apply when fancy tab bar is in use)
         background = tab_empty_bg,
         active_tab = {bg_color = "#00cc00", fg_color = tab_text_fg,},
         inactive_tab = {bg_color = "#005500", fg_color = tab_text_fg,},
         inactive_tab_hover = {bg_color = "#008800", fg_color = tab_text_fg,},
         new_tab = {bg_color = "#999900", fg_color = tab_text_fg,},
         new_tab_hover = {bg_color = "#dddd00", fg_color = tab_text_fg,}
      }
   },
   tab_bar_style = {
    -- new_tab = "",
    new_tab = wezterm.format({
      {Background={Color=tab_text_fg}},
      {Foreground={Color="#003300"}},
      {Text=SOLID_LEFT_ARROW},
      {Background={Color="#003300"}},
      {Foreground={Color=tab_text_fg}},
      {Text="+"},
      {Background={Color=tab_text_fg}},
      {Foreground={Color="#003300"}},
      {Text=SOLID_RIGHT_ARROW},
    }),
   },
}

Expected Behavior

tmux-like behaviour, allowing multiple views into the same workspace configurations.

Logs

No response

Anything else?

No response

Lenbok commented 2 years ago

(Also weirdly, when I clicked the close button on on of the GUI terminals, it immediately opened a new one, I think into a different workspace)

wez commented 2 years ago

This is improved by 39bb8b3f3943cb02aa723fb41c6a55e0195bebbc but I don't know if that's covered everything that you were seeing. Please give that a go and let me know!

It should show up as an AppImage build ~30 minutes from now.

Lenbok commented 2 years ago

I'm excited! I'll check it out tomorrow, thanks!

Lenbok commented 2 years ago

Checking this out now. Since I last updated it's not happy with my workspace keybindings. I have entries like:

      { key = "W", mods = "LEADER",       action = wezterm.action{SwitchToWorkspace = {name = "WORK"}}},

And wezterm is complaining:

callback error
stack traceback:
        [C]: in function 'wezterm.action'
        [string "/home2/len/.config/wezterm/wezterm.lua"]:230: in main chunk
caused by: error converting Lua table to KeyAssignment (Error processing
SwitchToWorkspace::spawn: missing field)

I thought spawn was optional?

Lenbok commented 2 years ago

For now I've worked around by adding spawn = {} to the SwitchToWorkspace parameters.

In terms of keeping the windows / workspaces straight it seems to be working much better (I've only been testing a unix domain, haven't tried ssh yet). A couple of remaining issues:

Related to workspace handling, but not directly related to this bugfix:

wez commented 2 years ago

https://github.com/wez/wezterm/commit/94039c473bebbf35d4994863965ebd58c3647218 should fix the spawn optional field issue.

wez commented 2 years ago
  • If I click on the OS window close button, the window immediately re-opens containing all workspaces except the one that was open at the time the window was closed. I would expect that the window would stay gone.

Hmm, the current behavior comes from before we had a formal detach action. The gui currently requires that there be at least 1 window open, because there's no way to interact after the last one closes. It picks a workspace from the remaining workspaces and adopts that for the window. The close logic doesn't (easily) know whether all the panes are remote/detachable or whether you might lose/forget some.

When closing the window in this situation, would you prefer that it detach completely and quit? What do you think should happen if (in the future) a window/workspace pair has a mixture of local and remote panes, and that pair is not currently active in the window?

  • For some reason, mouse clicking on the tabs within the window is often super laggy (like a few seconds) between the click and the tab view changing. Switching tabs via key binds is fast.

Weird!

Related to workspace handling, but not directly related to this bugfix:

  • Is there a config setting to change the name of the default workspace?
  • Is there a way to have SwitchToWorkspace prompt for a workspace name?

Not currently.

Lenbok commented 2 years ago

When closing the window in this situation, would you prefer that it detach completely and quit? What do you think should happen if (in the future) a window/workspace pair has a mixture of local and remote panes, and that pair is not currently active in the window?

Yes, I think so. I want that window gone (e.g. finished for the day, need to reboot the local PC, etc), but will want to reattach at some point in the future.

But maybe I also have tmux brain damage. I never have mixtures of local and remote panes within a workspace. Or even remote domains - if I need panes on another host for that project they are by ssh'ing from the "base domain", rather than directly as their own domain from my local pc (as that would mean duplicating ssh setup for those extra hosts onto multiple machines). I don't even know what it would mean to have a workspace containing panes from different native domains - it seems like you couldn't connect/attach to that workspace (aka project) from another machine and have the same set of panes all available.

I also don't have workspaces from different domains within the same GUI window (really since it's not possible in tmux-land without nesting sessions, with all the leader key gymnastics that involves). Currently I "switch domains" by selecting a different gui window).

Would be interested to hear other people's workflows.

wez commented 1 year ago

I'm going to close this out as there are now solutions to all of the things mentioned in this thread in main:

github-actions[bot] commented 1 year ago

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.