1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-22 04:56:12 +03:00

add wezterm start --new-tab and wezterm connect --new-tab

refs: https://github.com/wez/wezterm/discussions/4854
refs: https://github.com/wez/wezterm/discussions/4946
This commit is contained in:
Wez Furlong 2024-02-03 07:35:31 -07:00
parent c3d37f9eda
commit 99c17b166f
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
28 changed files with 131 additions and 63 deletions

View File

@ -1027,7 +1027,7 @@ _wezterm() {
return 0
;;
wezterm__connect)
opts="-h --class --workspace --position --help <DOMAIN_NAME> [PROG]..."
opts="-h --new-tab --class --workspace --position --help <DOMAIN_NAME> [PROG]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@ -1725,7 +1725,7 @@ _wezterm() {
return 0
;;
wezterm__start)
opts="-e -h --no-auto-connect --always-new-process --cwd --class --workspace --position --domain --attach --help [PROG]..."
opts="-e -h --no-auto-connect --always-new-process --new-tab --cwd --class --workspace --position --domain --attach --help [PROG]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0

View File

@ -23,6 +23,7 @@ complete -c wezterm -n "__fish_seen_subcommand_from start" -l position -d 'Overr
complete -c wezterm -n "__fish_seen_subcommand_from start" -l domain -d 'Name of the multiplexer domain section from the configuration to which you\'d like to connect. If omitted, the default domain will be used' -r
complete -c wezterm -n "__fish_seen_subcommand_from start" -l no-auto-connect -d 'If true, do not connect to domains marked as connect_automatically in your wezterm configuration file'
complete -c wezterm -n "__fish_seen_subcommand_from start" -l always-new-process -d 'If enabled, don\'t try to ask an existing wezterm GUI instance to start the command. Instead, always start the GUI in this invocation of wezterm so that you can wait for the command to complete by waiting for this wezterm process to finish'
complete -c wezterm -n "__fish_seen_subcommand_from start" -l new-tab -d 'When spawning into an existing GUI instance, spawn a new tab into the active window rather than spawn a new window'
complete -c wezterm -n "__fish_seen_subcommand_from start" -s e -d 'Dummy argument that consumes "-e" and does nothing. This is meant as a compatibility layer for supporting the widely adopted standard of passing the command to execute to the terminal via a "-e" option. This works because we then treat the remaining cmdline as trailing options, that will automatically be parsed via the existing "prog" option. This option exists only as a fallback. It is recommended to pass the command as a normal trailing command instead if possible'
complete -c wezterm -n "__fish_seen_subcommand_from start" -l attach -d 'When used with --domain, if the domain already has running panes, wezterm will simply attach and will NOT spawn the specified PROG. If you omit --attach when using --domain, wezterm will attach AND then spawn PROG'
complete -c wezterm -n "__fish_seen_subcommand_from start" -s h -l help -d 'Print help (see more with \'--help\')'
@ -38,6 +39,7 @@ complete -c wezterm -n "__fish_seen_subcommand_from serial" -s h -l help -d 'Pri
complete -c wezterm -n "__fish_seen_subcommand_from connect" -l class -d 'Override the default windowing system class. The default is "org.wezfurlong.wezterm". Under X11 and Windows this changes the window class. Under Wayland this changes the app_id. This changes the class for all windows spawned by this instance of wezterm, including error, update and ssh authentication dialogs' -r
complete -c wezterm -n "__fish_seen_subcommand_from connect" -l workspace -d 'Override the default workspace with the provided name. The default is "default"' -r
complete -c wezterm -n "__fish_seen_subcommand_from connect" -l position -d 'Override the position for the initial window launched by this process.' -r
complete -c wezterm -n "__fish_seen_subcommand_from connect" -l new-tab -d 'When spawning into an existing GUI instance, spawn a new tab into the active window rather than spawn a new window'
complete -c wezterm -n "__fish_seen_subcommand_from connect" -s h -l help -d 'Print help (see more with \'--help\')'
complete -c wezterm -n "__fish_seen_subcommand_from ls-fonts" -l text -d 'Explain which fonts are used to render the supplied text string' -r
complete -c wezterm -n "__fish_seen_subcommand_from ls-fonts" -l codepoints -d 'Explain which fonts are used to render the specified unicode code point sequence. Code points are comma separated hex values' -r

View File

@ -41,6 +41,7 @@ _arguments "${_arguments_options[@]}" \
'--domain=[Name of the multiplexer domain section from the configuration to which you'\''d like to connect. If omitted, the default domain will be used]:DOMAIN: ' \
'--no-auto-connect[If true, do not connect to domains marked as connect_automatically in your wezterm configuration file]' \
'--always-new-process[If enabled, don'\''t try to ask an existing wezterm GUI instance to start the command. Instead, always start the GUI in this invocation of wezterm so that you can wait for the command to complete by waiting for this wezterm process to finish]' \
'(--always-new-process)--new-tab[When spawning into an existing GUI instance, spawn a new tab into the active window rather than spawn a new window]' \
'-e[Dummy argument that consumes "-e" and does nothing. This is meant as a compatibility layer for supporting the widely adopted standard of passing the command to execute to the terminal via a "-e" option. This works because we then treat the remaining cmdline as trailing options, that will automatically be parsed via the existing "prog" option. This option exists only as a fallback. It is recommended to pass the command as a normal trailing command instead if possible]' \
'--attach[When used with --domain, if the domain already has running panes, wezterm will simply attach and will NOT spawn the specified PROG. If you omit --attach when using --domain, wezterm will attach AND then spawn PROG]' \
'-h[Print help (see more with '\''--help'\'')]' \
@ -76,6 +77,7 @@ _arguments "${_arguments_options[@]}" \
'--class=[Override the default windowing system class. The default is "org.wezfurlong.wezterm". Under X11 and Windows this changes the window class. Under Wayland this changes the app_id. This changes the class for all windows spawned by this instance of wezterm, including error, update and ssh authentication dialogs]:CLASS: ' \
'--workspace=[Override the default workspace with the provided name. The default is "default"]:WORKSPACE: ' \
'--position=[Override the position for the initial window launched by this process.]:POSITION: ' \
'--new-tab[When spawning into an existing GUI instance, spawn a new tab into the active window rather than spawn a new window]' \
'-h[Print help (see more with '\''--help'\'')]' \
'--help[Print help (see more with '\''--help'\'')]' \
':domain_name -- Name of the multiplexer domain section from the configuration to which you'\''d like to connect:' \

View File

@ -133,6 +133,12 @@ pub struct Config {
#[dynamic(default)]
pub switch_to_last_active_tab_when_closing_tab: bool,
/// When true, launching a new wezterm instance will prefer
/// to spawn a new tab into an existing instance.
/// Otherwise, it will spawn a new window.
#[dynamic(default)]
pub prefer_to_spawn_tabs: bool,
#[dynamic(default)]
pub window_frame: WindowFrameConfig,

View File

@ -27,6 +27,10 @@ As features stabilize some brief notes about them will accumulate here.
`NO_HINTING` when the dpi is >= 100, otherwise `DEFAULT`. #4902
#### New
* We now show the Lua version in the debug overlay. Thanks to @bbkane! #4943
* `wezterm start --new-tab` and `wezterm connect --new-tab` to request a new
tab rather than a new window when spawning via an existing GUI instance.
The new [prefer_to_spawn_tabs](config/lua/config/prefer_to_spawn_tabs.md)
option allows you to make this happen by default. ?4854 ?4946
#### Fixed
* It was not possible to specify `freetype_load_flags = 'DEFAULT'`. #4902
* macOS: fallback fonts could select thin or otherwise unspecified font

View File

@ -0,0 +1,16 @@
---
tags:
- spawn
---
# `prefer_to_spawn_tabs = false`
{{since('nightly')}}
If set to `true`, launching a new instance of `wezterm` will prefer to
spawn a new tab when it is able to connect to your already-running GUI
instance.
Otherwise, it will spawn a new window.
The default value for this option is `false`.

View File

@ -12,6 +12,10 @@ Arguments:
-- bash -l` will spawn bash as if it were a login shell
Options:
--new-tab
When spawning into an existing GUI instance, spawn a new tab into the
active window rather than spawn a new window
--class <CLASS>
Override the default windowing system class. The default is
"org.wezfurlong.wezterm". Under X11 and Windows this changes the

View File

@ -18,6 +18,10 @@ Options:
wezterm so that you can wait for the command to complete by waiting
for this wezterm process to finish
--new-tab
When spawning into an existing GUI instance, spawn a new tab into the
active window rather than spawn a new window
--cwd <CWD>
Specify the current working directory for the initially spawned
program

View File

@ -1277,6 +1277,33 @@ impl Client {
rx.recv().await.context("send_pdu recv")?
}
pub async fn resolve_pane_id(&self, pane_id: Option<PaneId>) -> anyhow::Result<PaneId> {
let pane_id: PaneId = match pane_id {
Some(p) => p,
None => {
if let Ok(pane) = std::env::var("WEZTERM_PANE") {
pane.parse()?
} else {
let mut clients = self.list_clients().await?.clients;
clients.retain(|client| client.focused_pane_id.is_some());
clients.sort_by(|a, b| b.last_input.cmp(&a.last_input));
if clients.is_empty() {
anyhow::bail!(
"--pane-id was not specified and $WEZTERM_PANE
is not set in the environment, and I couldn't
determine which pane was currently focused"
);
}
clients[0]
.focused_pane_id
.expect("to have filtered out above")
}
}
};
Ok(pane_id)
}
rpc!(ping, Ping = (), Pong);
rpc!(list_panes, ListPanes = (), ListPanesResponse);
rpc!(spawn_v2, SpawnV2, SpawnResponse);
@ -1313,7 +1340,7 @@ impl Client {
);
rpc!(kill_pane, KillPane, UnitResponse);
rpc!(set_client_id, SetClientId, UnitResponse);
rpc!(list_clients, GetClientList, GetClientListResponse);
rpc!(list_clients, GetClientList = (), GetClientListResponse);
rpc!(set_window_workspace, SetWindowWorkspace, UnitResponse);
rpc!(set_focused_pane_id, SetFocusedPane, UnitResponse);
rpc!(get_image_cell, GetImageCell, GetImageCellResponse);

View File

@ -40,6 +40,11 @@ pub struct StartCommand {
#[arg(long = "always-new-process")]
pub always_new_process: bool,
/// When spawning into an existing GUI instance, spawn a new
/// tab into the active window rather than spawn a new window.
#[arg(long, conflicts_with = "always_new_process")]
pub new_tab: bool,
/// Specify the current working directory for the initially
/// spawned program
#[arg(long = "cwd", value_parser, value_hint=ValueHint::DirPath)]
@ -203,6 +208,11 @@ pub struct ConnectCommand {
/// to which you'd like to connect
pub domain_name: String,
/// When spawning into an existing GUI instance, spawn a new
/// tab into the active window rather than spawn a new window.
#[arg(long)]
pub new_tab: bool,
/// Override the default windowing system class.
/// The default is "org.wezfurlong.wezterm".
/// Under X11 and Windows this changes the window class.

View File

@ -513,6 +513,7 @@ impl Publish {
config: &ConfigHandle,
workspace: Option<&str>,
domain: SpawnTabDomain,
new_tab: bool,
) -> anyhow::Result<bool> {
if let Publish::TryPathOrPublish(gui_sock) = &self {
let dom = config::UnixDomain {
@ -542,10 +543,41 @@ impl Publish {
"Running GUI has different config from us, will start a new one"
);
}
let window_id = if new_tab || config.prefer_to_spawn_tabs {
if let Ok(pane_id) = client.resolve_pane_id(None).await {
let panes = client.list_panes().await?;
let mut window_id = None;
'outer: for tabroot in panes.tabs {
let mut cursor = tabroot.into_tree().cursor();
loop {
if let Some(entry) = cursor.leaf_mut() {
if entry.pane_id == pane_id {
window_id.replace(entry.window_id);
break 'outer;
}
}
match cursor.preorder_next() {
Ok(c) => cursor = c,
Err(_) => break,
}
}
}
window_id
} else {
None
}
} else {
None
};
client
.spawn_v2(codec::SpawnV2 {
domain,
window_id: None,
window_id,
command,
command_dir: None,
size: config.initial_size(0),
@ -713,6 +745,7 @@ fn run_terminal_gui(opts: StartCommand, default_domain_name: Option<String>) ->
Some(name) => SpawnTabDomain::DomainName(name.to_string()),
None => SpawnTabDomain::DefaultDomain,
},
opts.new_tab,
)? {
return Ok(());
}
@ -1194,6 +1227,7 @@ fn run() -> anyhow::Result<()> {
workspace: connect.workspace,
position: connect.position,
prog: connect.prog,
new_tab: connect.new_tab,
always_new_process: true,
attach: true,
_cmd: false,

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use clap::Parser;
use mux::pane::PaneId;
use wezterm_client::client::Client;
@ -14,7 +13,7 @@ pub struct ActivatePane {
impl ActivatePane {
pub async fn run(&self, client: Client) -> anyhow::Result<()> {
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
client
.set_focused_pane_id(codec::SetFocusedPane { pane_id })
.await?;

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use clap::builder::PossibleValue;
use clap::Parser;
use config::keyassignment::PaneDirection;
@ -20,7 +19,7 @@ pub struct ActivatePaneDirection {
impl ActivatePaneDirection {
pub async fn run(&self, client: Client) -> anyhow::Result<()> {
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
client
.activate_pane_direction(codec::ActivatePaneDirection {
pane_id,

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use clap::Parser;
use mux::pane::PaneId;
use mux::tab::TabId;
@ -82,7 +81,7 @@ impl ActivateTab {
tab_id
} else {
// Find the current tab from the pane id
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
let current_tab_id = pane_id_to_tab_id
.get(&pane_id)
.copied()

View File

@ -1,5 +1,4 @@
use crate::cli::activate_pane_direction::PaneDirectionParser;
use crate::cli::resolve_pane_id;
use clap::Parser;
use codec::AdjustPaneSize;
use config::keyassignment::PaneDirection;
@ -23,7 +22,7 @@ pub struct CliAdjustPaneSize {
impl CliAdjustPaneSize {
pub async fn run(&self, client: Client) -> anyhow::Result<()> {
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
match client
.adjust_pane_size(AdjustPaneSize {
pane_id,

View File

@ -1,5 +1,4 @@
use crate::cli::activate_pane_direction::PaneDirectionParser;
use crate::cli::resolve_pane_id;
use clap::Parser;
use config::keyassignment::PaneDirection;
use mux::pane::PaneId;
@ -20,7 +19,7 @@ pub struct GetPaneDirection {
impl GetPaneDirection {
pub async fn run(&self, client: Client) -> anyhow::Result<()> {
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
let response = client
.get_pane_direction(codec::GetPaneDirection {
pane_id,

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use clap::Parser;
use mux::pane::PaneId;
use termwiz_funcs::lines_to_escapes;
@ -37,7 +36,7 @@ pub struct GetText {
impl GetText {
pub async fn run(self, client: Client) -> anyhow::Result<()> {
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
let info = client
.get_dimensions(codec::GetPaneRenderableDimensions { pane_id })

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use clap::Parser;
use mux::pane::PaneId;
use wezterm_client::client::Client;
@ -14,7 +13,7 @@ pub struct KillPane {
impl KillPane {
pub async fn run(&self, client: Client) -> anyhow::Result<()> {
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
client.kill_pane(codec::KillPane { pane_id }).await?;
Ok(())
}

View File

@ -16,7 +16,7 @@ pub struct ListClientsCommand {
impl ListClientsCommand {
pub async fn run(&self, client: Client) -> anyhow::Result<()> {
let out = std::io::stdout();
let clients = client.list_clients(codec::GetClientList).await?;
let clients = client.list_clients().await?;
match self.format {
CliOutputFormatKind::Json => {
let clients = clients

View File

@ -1,6 +1,5 @@
use anyhow::anyhow;
use clap::Parser;
use mux::pane::PaneId;
use std::ffi::OsString;
use wezterm_client::client::Client;
@ -211,33 +210,6 @@ pub fn run_cli(opts: &crate::Opt, cli: CliCommand) -> anyhow::Result<()> {
}
}
pub async fn resolve_pane_id(client: &Client, pane_id: Option<PaneId>) -> anyhow::Result<PaneId> {
let pane_id: PaneId = match pane_id {
Some(p) => p,
None => {
if let Ok(pane) = std::env::var("WEZTERM_PANE") {
pane.parse()?
} else {
let mut clients = client.list_clients(codec::GetClientList).await?.clients;
clients.retain(|client| client.focused_pane_id.is_some());
clients.sort_by(|a, b| b.last_input.cmp(&a.last_input));
if clients.is_empty() {
anyhow::bail!(
"--pane-id was not specified and $WEZTERM_PANE
is not set in the environment, and I couldn't
determine which pane was currently focused"
);
}
clients[0]
.focused_pane_id
.expect("to have filtered out above")
}
}
};
Ok(pane_id)
}
pub fn resolve_relative_cwd(cwd: Option<OsString>) -> anyhow::Result<Option<String>> {
match cwd {
None => Ok(None),

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use clap::Parser;
use mux::pane::PaneId;
use mux::window::WindowId;
@ -32,7 +31,7 @@ pub struct MovePaneToNewTab {
impl MovePaneToNewTab {
pub async fn run(&self, client: Client) -> anyhow::Result<()> {
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
let window_id = if self.new_window {
None
} else {

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use clap::Parser;
use mux::pane::PaneId;
use std::collections::HashMap;
@ -47,7 +46,7 @@ impl RenameWorkspace {
workspace
} else {
// Find the current tab from the pane id
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
pane_id_to_workspace
.get(&pane_id)
.ok_or_else(|| anyhow::anyhow!("unable to resolve current workspace"))?

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use anyhow::Context;
use clap::Parser;
use mux::pane::PaneId;
@ -23,7 +22,7 @@ pub struct SendText {
impl SendText {
pub async fn run(self, client: Client) -> anyhow::Result<()> {
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
let data = match self.text {
Some(text) => text,

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use clap::Parser;
use mux::pane::PaneId;
use mux::tab::TabId;
@ -46,7 +45,7 @@ impl SetTabTitle {
tab_id
} else {
// Find the current tab from the pane id
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
pane_id_to_tab_id
.get(&pane_id)
.copied()

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use clap::Parser;
use mux::pane::PaneId;
use mux::window::WindowId;
@ -47,7 +46,7 @@ impl SetWindowTitle {
window_id
} else {
// Find the current tab from the pane id
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
pane_id_to_window_id
.get(&pane_id)
.copied()

View File

@ -1,4 +1,4 @@
use crate::cli::{resolve_pane_id, resolve_relative_cwd};
use crate::cli::resolve_relative_cwd;
use clap::{Parser, ValueHint};
use config::keyassignment::SpawnTabDomain;
use config::ConfigHandle;
@ -58,7 +58,7 @@ impl SpawnCommand {
match self.window_id {
Some(w) => Some(w),
None => {
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
let panes = client.list_panes().await?;
let mut window_id = None;

View File

@ -1,4 +1,4 @@
use crate::cli::{resolve_pane_id, resolve_relative_cwd};
use crate::cli::resolve_relative_cwd;
use clap::{Parser, ValueHint};
use mux::pane::PaneId;
use mux::tab::{SplitDirection, SplitRequest, SplitSize};
@ -69,7 +69,7 @@ pub struct SplitPane {
impl SplitPane {
pub async fn run(self, client: Client) -> anyhow::Result<()> {
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
let direction = if self.left || self.right || self.horizontal {
SplitDirection::Horizontal

View File

@ -1,4 +1,3 @@
use crate::cli::resolve_pane_id;
use anyhow::{anyhow, Result};
use clap::Parser;
use codec::SetPaneZoomed;
@ -54,7 +53,7 @@ impl ZoomPane {
}
}
let pane_id = resolve_pane_id(&client, self.pane_id).await?;
let pane_id = client.resolve_pane_id(self.pane_id).await?;
let containing_tab_id = pane_id_to_tab_id
.get(&pane_id)
.copied()