wez / wezterm

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

Switching workspaces opens windows when it shouldn't #2984

Open Lenbok opened 1 year ago

Lenbok commented 1 year ago

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

Linux X11

Which Wayland compositor or X11 Window manager(s) are you using?

xfce

WezTerm version

wezterm 20230119-074737-730c41b7

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

When switching to a new workspace, sometimes wezterm uses the existing window, and sometimes it doesn't and opens a new window (leaving the old one present). It should always re-use the existing window. When closing one of the workspace windows, sometimes it closes, and sometimes it immediately re-opens another window.

To Reproduce

Ensure no wezterm (or mux server) is already running, then start with the provided config:

$ wezterm-nightly --config-file wezterm.lua

You'll get one window, on a workspace named after the current host. I think it's on the local domain.

At this point a brand new window appears - it should have re-used the existing window. Both windows are on the just selected workspace.

Configuration

local wezterm = require("wezterm");

local hostname = wezterm.hostname();

-- Dynamically build the unix domains configuration element.
-- This lets us use an ssh proxy for hosts except for the current host.
--
-- Note: We are using an ssh proxy to permit port forwarding (since wezterm
-- native ssh domains do not permit this.)
local unix_hosts = {"localhost", "ruru", "noir", "tron" }
local unix_domains = {}
while #unix_hosts > 0 do
   local host = table.remove(unix_hosts, 1)
   if host == "localhost" or host == hostname then
      table.insert(unix_domains, {name = host  })
   else
      table.insert(unix_domains, {name = host, proxy_command = {"ssh", "-T", host, "wezterm-nightly", "cli", "proxy"}})
   end
end

return {
   default_workspace = hostname,
   unix_domains = unix_domains,
   disable_default_key_bindings = true,
   exit_behavior = "Close",

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

   leader = { key = "`", timeout_milliseconds = 2000},
   keys = {
      -- Send "`" key to the terminal when pressed after LEADER
      { key = "`", mods = "LEADER",       action = wezterm.action{SendString = "`"}},
      { key = "\t",mods = "LEADER",       action = "ActivateLastTab"},
      { key = " ", mods = "LEADER",       action = "ShowLauncher"},
      { key = " ", mods = "LEADER|CTRL",       action = wezterm.action{ShowLauncherArgs = {flags = "FUZZY|DOMAINS|WORKSPACES|TABS|LAUNCH_MENU_ITEMS|KEY_ASSIGNMENTS"}}},

      { key = "s", mods = "LEADER",       action = wezterm.action{ShowLauncherArgs = {title = "Workspaces", flags = "FUZZY|WORKSPACES"}}},
      -- Experimental, associate workspace with domain, see https://github.com/wez/wezterm/issues/1874
      -- Should switch to the "default" workspace, (named after this host).
      { key = "l", mods = "LEADER|CTRL",       action = wezterm.action{SwitchToWorkspace = {
                                                                     name = hostname,
                                                                     spawn = {
                                                                        domain = {
                                                                           DomainName = hostname
                                                                        }
                                                                     }
      }}},
      -- A project workspace, on the current host
      { key = "s", mods = "LEADER|SHIFT",       action = wezterm.action{SwitchToWorkspace = {
                                                                     name = "SomethingElse",
                                                                     spawn = {
                                                                        domain = {
                                                                           DomainName = hostname
                                                                        }
                                                                     }
      }}},
      -- A project workspace, possibly on a remote host but here we just use the local host
      { key = "p", mods = "LEADER|SHIFT",       action = wezterm.action{SwitchToWorkspace = {
                                                                     name = "Project X",
                                                                     spawn = {
                                                                        domain = {
                                                                           DomainName = hostname
                                                                        }
                                                                     }
      }}},

   },
}

Expected Behavior

It should have re-used the existing window.

Logs

No response

Anything else?

No response

tale commented 1 year ago

Hey, I just found this issue and I can confirm that it is still a problem.

loops commented 5 months ago

The code in SwitchToWorkspace seems to fail when the spawn is in a remote domain. So the final line in this snippet:

            let switcher = crate::frontend::WorkspaceSwitcher::new(&name);
            mux.set_active_workspace(&name);

            if mux.iter_windows_in_workspace(&name).is_empty() {

Fails to find any windows in the workspace, because the test is run before the actual connection to the remote server has completed. And a new window is mistakenly created.

This also means that there is no way to attach to an already running remote mux server, without also spawning a new process at the same time. It would be nice to attach to the remote server and not have to kill a spurious shell.

Disclaimer: I have no experience with Rust, so this might be wrong. Just hoping it might be a useful clue for someone who knows what they're doing.

loops commented 5 months ago

Solved my local problem and posting it here for whatever value, if any, it might offer others...

function switch_to_domain(dom, window, pane)
    local ws={name=dom, spawn={domain={DomainName=dom}}}
    local action=wezterm.action{SwitchToWorkspace=ws}
    window:perform_action(action, pane)
end

function toggle_domain(dom, window, pane)
    if window:active_workspace() == dom
    then
        switch_to_domain("default", window, pane)
    else
        wezterm.mux.get_domain(dom):attach()
        switch_to_domain(dom, window, pane)
    end
end

function toggle_domain_action(dom)
    return wezterm.action_callback(
        function(win, pane)
            toggle_domain(dom, win, pane)
        end
    )
end

and then:

{key="k", mods="CTRL|SHIFT", action=toggle_domain_action("kia")},
{key="o", mods="CTRL|SHIFT", action=toggle_domain_action("ox")},

Creating two keys: "k" which toggles the "kia" ssh domain, and "o" which toggles the "ox" ssh domain. A workspace, the same name as the domain is created if it doesn't already exist. If the workspace already exists and is the current workspace, those keystrokes instead toggle to the default workspace.

Probably the only potential clue is the line :

  wezterm.mux.get_domain(dom):attach()

Which attaches the domain first, before attempting to switch to it. This removes the spurious window that I was seeing here. But I fear it might be a slightly different problem than others were reporting above.