Run keybinding improvements (#5701)

* add ORIGIN_PANE_ID to Run commands started from a keybinding

* RunInBackground

* add pr
This commit is contained in:
Aram Drevekenin
2026-10-09 16:14:47 +02:00
committed by GitHub
parent 5586646d3f
commit 333d7898a8
38 changed files with 644 additions and 30 deletions
+2
View File
@@ -30,6 +30,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* fix: support horizontal scroll for programs that request it (https://github.com/zellij-org/zellij/pull/4860)
* feat: allow config to dangerously disable https certificate enforcement on non-localhost (https://github.com/zellij-org/zellij/pull/5694)
* feat: reorder tabs with mouse, configurable mouse bindings (https://github.com/zellij-org/zellij/pull/5697)
* feat: pass `ZELLIJ_ORIGIN_PANE_ID` to `Run` keybindings and allow running in background (https://github.com/zellij-org/zellij/pull/5701)
## [0.45.1] - 2026-08-28
* fix: nested-session detection over SSH (https://github.com/zellij-org/zellij/pull/5522)
@@ -0,0 +1,82 @@
#![cfg(unix)]
use zellij_integration_tests::{
claim_first_terminal_and_wait_for_prompt, keys, split_right_and_wait_for_prompt, TestRunner,
TestSession, TERMINAL_SIZE,
};
const ORIGIN_PANE_ID: &str = "ZELLIJ_ORIGIN_PANE_ID";
fn start_with_run_bindings() -> TestSession {
TestRunner::new(TERMINAL_SIZE)
.with_config(
r#"
keybinds {
shared {
bind "Alt y" { Run "origin-test" { floating true; }; }
bind "Alt u" { MoveFocus "Left"; Run "origin-test" { floating true; }; }
}
}
"#,
)
.start()
}
#[test]
fn a_command_run_from_a_keybinding_knows_the_pane_it_was_started_from() {
let mut zellij = start_with_run_bindings();
let first_terminal = claim_first_terminal_and_wait_for_prompt(&zellij);
let second_terminal = split_right_and_wait_for_prompt(&zellij);
zellij.send_stdin(&keys::alt('y'));
let command_terminal = zellij.expect_pty_spawn();
let expected_origin = format!("terminal_{}", second_terminal.terminal_id());
assert_eq!(
command_terminal.env_var(ORIGIN_PANE_ID).as_deref(),
Some(expected_origin.as_str())
);
assert_eq!(first_terminal.env_var(ORIGIN_PANE_ID), None);
assert_eq!(second_terminal.env_var(ORIGIN_PANE_ID), None);
zellij.quit();
}
#[test]
fn the_origin_pane_reflects_focus_changes_earlier_in_the_same_keybinding() {
let mut zellij = start_with_run_bindings();
let first_terminal = claim_first_terminal_and_wait_for_prompt(&zellij);
split_right_and_wait_for_prompt(&zellij);
zellij.send_stdin(&keys::alt('u'));
let command_terminal = zellij.expect_pty_spawn();
let expected_origin = format!("terminal_{}", first_terminal.terminal_id());
assert_eq!(
command_terminal.env_var(ORIGIN_PANE_ID).as_deref(),
Some(expected_origin.as_str())
);
zellij.quit();
}
#[test]
fn a_command_run_from_a_focused_plugin_pane_gets_a_plugin_origin() {
let mut zellij = start_with_run_bindings();
claim_first_terminal_and_wait_for_prompt(&zellij);
zellij.send_stdin(&keys::ctrl('o'));
zellij.send_stdin(&keys::key('a'));
zellij.wait_until("about plugin shown", |grid_snapshot| {
grid_snapshot.contains("About Zellij")
});
zellij.send_stdin(&keys::alt('y'));
let command_terminal = zellij.expect_pty_spawn();
let origin = command_terminal
.env_var(ORIGIN_PANE_ID)
.expect("origin pane id is set");
let plugin_id = origin
.strip_prefix("plugin_")
.unwrap_or_else(|| panic!("expected a plugin origin, got {}", origin));
assert!(plugin_id.parse::<u32>().is_ok(), "{}", origin);
zellij.quit();
}
@@ -0,0 +1,101 @@
#![cfg(unix)]
use std::collections::HashMap;
use std::path::Path;
use std::time::Instant;
use zellij_integration_tests::{
claim_first_terminal_and_wait_for_prompt, default_timeout, keys,
split_right_and_wait_for_prompt, TestRunner, TestSession, TERMINAL_SIZE,
};
fn start_with_background_binding(env_file: &Path) -> TestSession {
let config = format!(
r#"
keybinds {{
shared {{
bind "Alt y" {{
Run "sh" "-c" "env > \"$1.tmp\" && mv \"$1.tmp\" \"$1\"" "sh" "{}" {{
background true;
}};
}}
bind "Alt n" {{ NewPane; }}
}}
}}
"#,
env_file.display()
);
TestRunner::new(TERMINAL_SIZE).with_config(&config).start()
}
fn wait_for_env_file(env_file: &Path) -> HashMap<String, String> {
let deadline = Instant::now() + default_timeout();
loop {
if let Ok(contents) = std::fs::read_to_string(env_file) {
return contents
.lines()
.filter_map(|line| line.split_once('='))
.map(|(name, value)| (name.to_owned(), value.to_owned()))
.collect();
}
assert!(
Instant::now() < deadline,
"background command did not write {}",
env_file.display()
);
std::thread::sleep(std::time::Duration::from_millis(10));
}
}
#[test]
fn a_background_command_runs_without_a_pane_and_knows_the_focused_terminal() {
let temp_dir = tempfile::tempdir().unwrap();
let env_file = temp_dir.path().join("env");
let mut zellij = start_with_background_binding(&env_file);
claim_first_terminal_and_wait_for_prompt(&zellij);
let second_terminal = split_right_and_wait_for_prompt(&zellij);
zellij.send_stdin(&keys::alt('y'));
let env = wait_for_env_file(&env_file);
let expected_origin = format!("terminal_{}", second_terminal.terminal_id());
assert_eq!(env.get("ZELLIJ_ORIGIN_PANE_ID"), Some(&expected_origin));
assert_eq!(
env.get("ZELLIJ_PANE_ID"),
Some(&second_terminal.terminal_id().to_string())
);
zellij.send_stdin(&keys::alt('n'));
let next_terminal = zellij.expect_pty_spawn();
assert_eq!(
next_terminal.terminal_id(),
second_terminal.terminal_id() + 1
);
zellij.quit();
}
#[test]
fn a_background_command_from_a_focused_plugin_pane_has_no_terminal_pane_id() {
let temp_dir = tempfile::tempdir().unwrap();
let env_file = temp_dir.path().join("env");
let mut zellij = start_with_background_binding(&env_file);
claim_first_terminal_and_wait_for_prompt(&zellij);
zellij.send_stdin(&keys::ctrl('o'));
zellij.send_stdin(&keys::key('a'));
zellij.wait_until("about plugin shown", |grid_snapshot| {
grid_snapshot.contains("About Zellij")
});
zellij.send_stdin(&keys::alt('y'));
let env = wait_for_env_file(&env_file);
let origin = env
.get("ZELLIJ_ORIGIN_PANE_ID")
.expect("origin pane id is set");
let plugin_id = origin
.strip_prefix("plugin_")
.unwrap_or_else(|| panic!("expected a plugin origin, got {}", origin));
assert!(plugin_id.parse::<u32>().is_ok(), "{}", origin);
assert_eq!(env.get("ZELLIJ_PANE_ID"), None);
zellij.quit();
}
+65
View File
@@ -30,6 +30,7 @@ use std::sync::{
};
use std::time::{Duration, Instant};
use crate::os_input_output::PaneEnv;
use crate::panes::PaneId;
use crate::plugins::{PluginId, PluginInstruction};
use crate::pty::PtyInstruction;
@@ -54,6 +55,12 @@ pub enum BackgroundJob {
PathBuf,
BTreeMap<String, String>,
), // command, args, env_variables, cwd, context
RunInBackground {
command: PathBuf,
args: Vec<String>,
cwd: Option<PathBuf>,
env: PaneEnv,
},
WebRequest(
PluginId,
ClientId,
@@ -93,6 +100,7 @@ impl From<&BackgroundJob> for BackgroundJobContext {
BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo,
BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo,
BackgroundJob::RunCommand(..) => BackgroundJobContext::RunCommand,
BackgroundJob::RunInBackground { .. } => BackgroundJobContext::RunInBackground,
BackgroundJob::WebRequest(..) => BackgroundJobContext::WebRequest,
BackgroundJob::ReportPluginList(..) => BackgroundJobContext::ReportPluginList,
BackgroundJob::RenderToClients => BackgroundJobContext::ReportPluginList,
@@ -371,6 +379,14 @@ pub(crate) fn background_jobs_main(
}
});
},
BackgroundJob::RunInBackground {
command,
args,
cwd,
env,
} => {
runtime.spawn(run_in_background(command, args, cwd, env));
},
BackgroundJob::WebRequest(plugin_id, client_id, url, verb, headers, body, context) => {
runtime.spawn({
let senders = bus.senders.clone();
@@ -1080,3 +1096,52 @@ mod tests {
}
}
}
async fn run_in_background(
command: PathBuf,
args: Vec<String>,
cwd: Option<PathBuf>,
env: PaneEnv,
) {
let mut process = tokio::process::Command::new(&command);
process
.args(&args)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped());
for (name, value) in env {
match value {
Some(value) => process.env(name, value),
None => process.env_remove(name),
};
}
if let Some(cwd) = cwd {
if cwd.is_dir() {
process.current_dir(cwd);
} else {
log::error!(
"Cannot run '{}' in '{}': not a folder",
command.display(),
cwd.display()
);
}
}
match process.output().await {
Ok(output) if !output.status.success() => {
log::error!(
"Background command '{}' exited with {}: {}",
command.display(),
output.status,
String::from_utf8_lossy(&output.stderr).trim_end()
);
},
Ok(_) => {},
Err(e) => {
log::error!(
"Failed to start background command '{}': {}",
command.display(),
e
);
},
}
}
@@ -44,5 +44,6 @@ Some(
stdout_message: None,
},
),
None,
),
)
@@ -26,5 +26,6 @@ Some(
},
),
false,
None,
),
)
@@ -46,5 +46,6 @@ Some(
},
),
false,
None,
),
)
@@ -41,5 +41,6 @@ Some(
},
),
false,
None,
),
)
@@ -2018,6 +2018,7 @@ fn open_file_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(self_pane_id(env))),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
None,
);
let _ = env.senders.send_to_pty(pty_instr);
@@ -2065,6 +2066,7 @@ fn open_file_floating_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(self_pane_id(env))),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
None,
);
let _ = env.senders.send_to_pty(pty_instr);
@@ -2105,6 +2107,7 @@ fn open_file_in_place_of_plugin(
close_plugin_after_replace,
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(self_pane_id(env))),
Some(NotificationEnd::new(completion_tx)),
None,
);
let _ = env.senders.send_to_pty(pty_instr);
@@ -2186,6 +2189,7 @@ fn open_terminal_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(self_pane_id(env))),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
None,
));
// Wait for completion
@@ -2265,6 +2269,7 @@ fn open_terminal_floating_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(self_pane_id(env))),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
None,
));
// Wait for completion
@@ -2342,6 +2347,7 @@ fn open_terminal_in_place_of_plugin(
close_plugin_after_replace,
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(self_pane_id(env))),
Some(NotificationEnd::new(completion_tx)),
None,
));
// Wait for completion
@@ -2399,6 +2405,7 @@ fn open_command_pane_in_place_of_plugin(
close_plugin_after_replace,
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(self_pane_id(env))),
Some(NotificationEnd::new(completion_tx)),
None,
));
// Wait for completion
@@ -2439,6 +2446,7 @@ fn open_terminal_pane_in_place_of_pane_id(
close_replaced_pane,
ClientTabIndexOrPaneId::PaneId(pane_id_to_replace.into()),
Some(NotificationEnd::new(completion_tx)),
None,
));
let result = wait_for_action_completion(
@@ -2498,6 +2506,7 @@ fn open_command_pane_in_place_of_pane_id(
close_replaced_pane,
ClientTabIndexOrPaneId::PaneId(pane_id_to_replace.into()),
Some(NotificationEnd::new(completion_tx)),
None,
));
let result = wait_for_action_completion(
@@ -2539,6 +2548,7 @@ fn open_edit_pane_in_place_of_pane_id(
close_replaced_pane,
ClientTabIndexOrPaneId::PaneId(pane_id_to_replace.into()),
Some(NotificationEnd::new(completion_tx)),
None,
);
let _ = env.senders.send_to_pty(pty_instr);
@@ -2652,6 +2662,7 @@ fn open_command_pane_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(self_pane_id(env))),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
None,
));
// Wait for completion
@@ -2767,6 +2778,7 @@ fn open_command_pane_floating_near_plugin(
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(self_pane_id(env))),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
None,
));
// Wait for completion
@@ -2881,6 +2893,7 @@ fn open_command_pane_background(
ClientTabIndexOrPaneId::ClientId(acting_client(env)),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
None,
));
// Wait for completion
+129 -17
View File
@@ -23,7 +23,7 @@ use zellij_utils::{
errors::prelude::*,
errors::{ContextType, PtyContext},
input::{
command::{OpenFilePayload, RunCommand, TerminalAction},
command::{OpenFilePayload, RunCommand, RunCommandAction, TerminalAction},
layout::{
FloatingPaneLayout, Layout, Run, RunPluginOrAlias, SwapFloatingLayout, SwapTiledLayout,
TabLayoutInfo, TiledPaneLayout,
@@ -36,6 +36,18 @@ use zellij_utils::{
pub type VteBytes = Vec<u8>;
pub type TabIndex = u32;
const ORIGIN_PANE_ID_ENV_VAR: &str = "ZELLIJ_ORIGIN_PANE_ID";
const PANE_ID_ENV_VAR: &str = "ZELLIJ_PANE_ID";
fn pane_env_with_origin(pane_env: &PaneEnv, origin_pane_id: Option<PaneId>) -> PaneEnv {
let mut pane_env = pane_env.clone();
pane_env.insert(
ORIGIN_PANE_ID_ENV_VAR.to_owned(),
origin_pane_id.map(|id| id.to_string()),
);
pane_env
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ClientTabIndexOrPaneId {
ClientId(ClientId),
@@ -56,6 +68,7 @@ pub enum PtyInstruction {
ClientTabIndexOrPaneId,
Option<NotificationEnd>, // completion signal
bool, // set_blocking
Option<PaneId>,
), // bool (if Some) is
// should_float, String is an optional pane name
OpenInPlaceEditor(
@@ -104,6 +117,7 @@ pub enum PtyInstruction {
bool, // close replaced pane
ClientTabIndexOrPaneId,
Option<NotificationEnd>, // completion signal
Option<PaneId>,
), // String is an optional pane name
DumpLayout(SessionLayoutMetadata, ClientId, Option<NotificationEnd>),
DumpLayoutToPlugin {
@@ -159,6 +173,12 @@ pub enum PtyInstruction {
},
UpdateAndReportCwds,
NotifyCwdFromOsc7(u32, PathBuf),
RunInBackground {
command: RunCommandAction,
client_id: ClientId,
origin_pane_id: Option<PaneId>,
completion_tx: Option<NotificationEnd>,
},
Exit,
}
@@ -195,6 +215,7 @@ impl From<&PtyInstruction> for PtyContext {
PtyInstruction::GetPaneCwd { .. } => PtyContext::GetPaneCwd,
PtyInstruction::UpdateAndReportCwds => PtyContext::UpdateAndReportCwds,
PtyInstruction::NotifyCwdFromOsc7(..) => PtyContext::NotifyCwdFromOsc7,
PtyInstruction::RunInBackground { .. } => PtyContext::RunInBackground,
PtyInstruction::Exit => PtyContext::Exit,
}
}
@@ -205,6 +226,7 @@ pub(crate) struct Pty {
pub bus: Bus<PtyInstruction>,
pub id_to_child_pid: HashMap<u32, u32>, // terminal_id => child pid
originating_plugins: HashMap<u32, OriginatingPlugin>,
origin_pane_ids: HashMap<u32, PaneId>,
debug_to_file: bool,
task_handles: HashMap<u32, JoinHandle<()>>, // terminal_id to join-handle
default_editor: Option<PathBuf>,
@@ -230,6 +252,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
client_or_tab_index,
completion_tx,
set_blocking,
origin_pane_id,
) => {
let err_context =
|| format!("failed to spawn terminal for {:?}", client_or_tab_index);
@@ -267,7 +290,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
_ => None,
};
match pty
.spawn_terminal(terminal_action, client_or_tab_index)
.spawn_terminal(terminal_action, client_or_tab_index, origin_pane_id)
.with_context(err_context)
{
Ok((pid, starts_held)) => {
@@ -367,6 +390,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
close_replaced_pane,
client_id_tab_index_or_pane_id,
completion_tx,
origin_pane_id,
) => {
let err_context = || {
format!(
@@ -398,7 +422,11 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
_ => None,
};
match pty
.spawn_terminal(terminal_action, client_id_tab_index_or_pane_id)
.spawn_terminal(
terminal_action,
client_id_tab_index_or_pane_id,
origin_pane_id,
)
.with_context(err_context)
{
Ok((pid, starts_held)) => {
@@ -465,6 +493,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
None,
))),
client_tab_index_or_pane_id,
None,
) {
Ok((pid, _starts_held)) => {
pty.bus
@@ -919,6 +948,16 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
PtyInstruction::NotifyCwdFromOsc7(terminal_id, path) => {
pty.notify_cwd_from_osc7(terminal_id, path);
},
PtyInstruction::RunInBackground {
command,
client_id,
origin_pane_id,
completion_tx,
} => {
pty.run_in_background(command, client_id, origin_pane_id)
.non_fatal();
drop(completion_tx);
},
PtyInstruction::Exit => break,
}
}
@@ -941,6 +980,7 @@ impl Pty {
task_handles: HashMap::new(),
default_editor,
originating_plugins: HashMap::new(),
origin_pane_ids: HashMap::new(),
post_command_discovery_hook,
pane_env,
plugin_cwds: HashMap::new(),
@@ -989,6 +1029,51 @@ impl Pty {
},
}
}
fn cwd_of_pane(&self, pane_id: &PaneId) -> Option<PathBuf> {
match pane_id {
PaneId::Terminal(terminal_id) => self
.id_to_child_pid
.get(terminal_id)
.and_then(|&pid| {
self.bus
.os_input
.as_ref()
.and_then(|input| input.get_cwd(pid))
})
.or_else(|| self.terminal_cwds.get(terminal_id).cloned()),
PaneId::Plugin(plugin_id) => self.plugin_cwds.get(plugin_id).cloned(),
}
}
fn run_in_background(
&self,
command: RunCommandAction,
client_id: ClientId,
origin_pane_id: Option<PaneId>,
) -> Result<()> {
let base_cwd = origin_pane_id
.or_else(|| self.active_panes.get(&client_id).copied())
.and_then(|pane_id| self.cwd_of_pane(&pane_id));
let cwd = match (command.cwd, base_cwd) {
(Some(cwd), Some(base_cwd)) if cwd.is_relative() => Some(base_cwd.join(cwd)),
(Some(cwd), _) => Some(cwd),
(None, base_cwd) => base_cwd,
};
let mut env = pane_env_with_origin(&self.pane_env, origin_pane_id);
let pane_id = match origin_pane_id {
Some(PaneId::Terminal(terminal_id)) => Some(terminal_id.to_string()),
_ => None,
};
env.insert(PANE_ID_ENV_VAR.to_owned(), pane_id);
self.bus
.senders
.send_to_background_jobs(BackgroundJob::RunInBackground {
command: command.command,
args: command.args,
cwd,
env,
})
.with_context(|| format!("failed to run command in background for client {client_id}"))
}
fn fill_cwd(&self, terminal_action: &mut TerminalAction, client_id: ClientId) {
let cwd = match terminal_action {
TerminalAction::RunCommand(run_command) => &mut run_command.cwd,
@@ -1042,6 +1127,7 @@ impl Pty {
&mut self,
terminal_action: Option<TerminalAction>,
client_or_tab_index: ClientTabIndexOrPaneId,
origin_pane_id: Option<PaneId>,
) -> Result<(u32, bool)> {
// bool is starts_held
let err_context = || format!("failed to spawn terminal for {:?}", client_or_tab_index);
@@ -1091,6 +1177,9 @@ impl Pty {
.context("couldn't get mutable reference to OS interface")
.and_then(|os_input| os_input.reserve_terminal_id())
.with_context(err_context)?;
if let Some(origin_pane_id) = origin_pane_id {
self.origin_pane_ids.insert(terminal_id, origin_pane_id);
}
return Ok((terminal_id, starts_held));
}
@@ -1144,7 +1233,8 @@ impl Pty {
}
}
});
let (terminal_id, reader, child_pid): (u32, Box<dyn AsyncReader>, Option<u32>) = self
let pane_env = pane_env_with_origin(&self.pane_env, origin_pane_id);
let spawn_result = self
.bus
.os_input
.as_mut()
@@ -1154,10 +1244,24 @@ impl Pty {
terminal_action,
quit_cb,
self.default_editor.clone(),
&self.pane_env,
&pane_env,
)
})
.with_context(err_context)?;
});
if let Some(origin_pane_id) = origin_pane_id {
let spawned_terminal_id = match &spawn_result {
Ok((terminal_id, ..)) => Some(*terminal_id),
Err(err) => match err.downcast_ref::<ZellijError>() {
Some(ZellijError::CommandNotFound { terminal_id, .. }) => Some(*terminal_id),
_ => None,
},
};
if let Some(spawned_terminal_id) = spawned_terminal_id {
self.origin_pane_ids
.insert(spawned_terminal_id, origin_pane_id);
}
}
let (terminal_id, reader, child_pid): (u32, Box<dyn AsyncReader>, Option<u32>) =
spawn_result.with_context(err_context)?;
let activity_flag = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let terminal_bytes = async_runtime().spawn({
let err_context =
@@ -1707,7 +1811,12 @@ impl Pty {
.as_mut()
.context("no OS I/O interface found")
.with_context(err_context)?
.spawn_terminal(cmd, quit_cb, self.default_editor.clone(), &self.pane_env)
.spawn_terminal(
cmd,
quit_cb,
self.default_editor.clone(),
&pane_env_with_origin(&self.pane_env, None),
)
.with_context(err_context)
{
Ok((terminal_id, reader, child_pid)) => {
@@ -1742,7 +1851,12 @@ impl Pty {
.as_mut()
.context("no OS I/O interface found")
.with_context(err_context)?
.spawn_terminal(shell, quit_cb, self.default_editor.clone(), &self.pane_env)
.spawn_terminal(
shell,
quit_cb,
self.default_editor.clone(),
&pane_env_with_origin(&self.pane_env, None),
)
.with_context(err_context)
{
Ok((terminal_id, reader, child_pid)) => {
@@ -1776,7 +1890,7 @@ impl Pty {
)),
quit_cb,
self.default_editor.clone(),
&self.pane_env,
&pane_env_with_origin(&self.pane_env, None),
)
.with_context(err_context)
{
@@ -1807,7 +1921,7 @@ impl Pty {
default_shell.clone(),
quit_cb,
self.default_editor.clone(),
&self.pane_env,
&pane_env_with_origin(&self.pane_env, None),
)
.with_context(err_context)
{
@@ -1852,6 +1966,7 @@ impl Pty {
self.terminal_cwds.remove(&id);
self.terminal_cmds.remove(&id);
self.terminal_foreground_cmds.remove(&id);
self.origin_pane_ids.remove(&id);
self.bus
.os_input
.as_ref()
@@ -1933,18 +2048,15 @@ impl Pty {
}
}
});
let pane_env =
pane_env_with_origin(&self.pane_env, self.origin_pane_ids.get(&id).copied());
let (reader, child_pid): (Box<dyn AsyncReader>, Option<u32>) = self
.bus
.os_input
.as_mut()
.context("no OS I/O interface found")
.and_then(|os_input| {
os_input.re_run_command_in_terminal(
id,
run_command,
quit_cb,
&self.pane_env,
)
os_input.re_run_command_in_terminal(id, run_command, quit_cb, &pane_env)
})
.with_context(err_context)?;
let activity_flag = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
+90 -4
View File
@@ -18,9 +18,9 @@ use uuid::Uuid;
use zellij_utils::{
channels::SenderWithContext,
data::{
BareKey, ConnectToSession, Direction, Event, InputMode, KeyModifier, ListPanesResponse,
ListTabsResponse, NewPanePlacement, PaneListEntry, ResizeStrategy, TabInfo,
UnblockCondition,
BareKey, ConnectToSession, Direction, Event, GetFocusedPaneInfoResponse, InputMode,
KeyModifier, ListPanesResponse, ListTabsResponse, NewPanePlacement, PaneListEntry,
ResizeStrategy, TabInfo, UnblockCondition,
},
envs,
errors::{prelude::*, ErrorContext},
@@ -261,7 +261,67 @@ fn new_pane_routing(
}
}
fn spawns_command_pane(action: &Action) -> bool {
match action {
Action::NewFloatingPane { command, .. }
| Action::NewInPlacePane { command, .. }
| Action::NewStackedPane { command, .. }
| Action::NewTiledPane { command, .. } => command.is_some(),
Action::RunInBackground { .. } => true,
_ => false,
}
}
fn focused_pane_id(senders: &ThreadSenders, client_id: ClientId) -> Option<PaneId> {
let (response_sender, response_receiver) = crossbeam::channel::bounded(1);
senders
.send_to_screen(ScreenInstruction::GetFocusedPaneInfo {
client_id,
response_channel: response_sender,
})
.ok()?;
match response_receiver.recv_timeout(Duration::from_millis(100)) {
Ok(GetFocusedPaneInfoResponse::Ok { pane_id, .. }) => Some(pane_id.into()),
Ok(GetFocusedPaneInfoResponse::Err(_)) => None,
Err(e) => {
log::error!(
"Failed to get focused pane for client {}: {:?}",
client_id,
e
);
None
},
}
}
pub(crate) fn route_action(
action: Action,
client_id: ClientId,
cli_client_id: Option<ClientId>,
pane_id: Option<PaneId>,
senders: ThreadSenders,
default_shell: Option<TerminalAction>,
seen_cli_pipes: Option<&mut HashSet<String>>,
default_mode: InputMode,
os_input: Option<Box<dyn ServerOsApi>>,
is_cli_client: bool,
) -> Result<(bool, Option<ActionCompletionResult>)> {
route_action_with_origin(
action,
client_id,
cli_client_id,
pane_id,
senders,
default_shell,
seen_cli_pipes,
default_mode,
os_input,
is_cli_client,
None,
)
}
fn route_action_with_origin(
action: Action,
client_id: ClientId,
cli_client_id: Option<ClientId>,
@@ -272,6 +332,7 @@ pub(crate) fn route_action(
default_mode: InputMode,
os_input: Option<Box<dyn ServerOsApi>>,
is_cli_client: bool,
origin_pane_id: Option<PaneId>,
) -> Result<(bool, Option<ActionCompletionResult>)> {
let mut should_break = false;
let err_context = || format!("failed to route action for client {client_id}");
@@ -739,6 +800,7 @@ pub(crate) fn route_action(
ClientTabIndexOrPaneId::ClientId(client_id),
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
None,
))
.with_context(err_context)?;
},
@@ -792,6 +854,7 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
notification_end,
set_pane_blocking,
None,
))
.with_context(err_context)?;
wait_forever = true;
@@ -819,6 +882,7 @@ pub(crate) fn route_action(
close_replaced_pane,
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
None,
)
} else {
let client_tab_index_or_paneid =
@@ -843,6 +907,7 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
None,
)
};
senders.send_to_pty(pty_instr).with_context(err_context)?;
@@ -884,6 +949,7 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
origin_pane_id,
))
.with_context(err_context)?;
},
@@ -924,6 +990,7 @@ pub(crate) fn route_action(
close_replaced_pane,
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
origin_pane_id,
))
.with_context(err_context)?;
},
@@ -984,6 +1051,7 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
origin_pane_id,
))
.with_context(err_context)?;
},
@@ -1015,6 +1083,7 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
origin_pane_id,
))
.with_context(err_context)?;
},
@@ -1073,9 +1142,20 @@ pub(crate) fn route_action(
client_tab_index_or_paneid,
Some(NotificationEnd::new(completion_tx)),
false, // set_blocking
None,
))
.with_context(err_context)?;
},
Action::RunInBackground { command } => {
senders
.send_to_pty(PtyInstruction::RunInBackground {
command,
client_id,
origin_pane_id: origin_pane_id.or(pane_id),
completion_tx: Some(NotificationEnd::new(completion_tx)),
})
.with_context(err_context)?;
},
Action::CloseFocus => {
senders
.send_to_screen(ScreenInstruction::CloseFocusedPane(
@@ -2642,7 +2722,12 @@ pub(crate) fn route_thread_main(
cli_client_id: None,
});
match route_action(
let origin_pane_id = if spawns_command_pane(&action) {
focused_pane_id(&senders, client_id)
} else {
None
};
match route_action_with_origin(
action,
client_id,
None,
@@ -2653,6 +2738,7 @@ pub(crate) fn route_thread_main(
client_input_mode,
Some(os_input.clone()),
false,
origin_pane_id,
) {
Ok(route_action_should_break) => {
if route_action_should_break.0 {
+1
View File
@@ -2722,6 +2722,7 @@ impl Tab {
client_id_or_tab_index,
completion_tx,
false, // set_blocking
None,
);
self.senders
.send_to_pty(instruction)
@@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
@@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: Some(100), cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: Some(100), cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
@@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: Some(Down), borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: Some(Down), borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
@@ -34,5 +34,6 @@ Some(
stdout_message: None,
},
),
None,
),
)
@@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", new_pane_instruction)"
---
Some(SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Tiled { direction: Some(Right), borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false))
Some(SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Tiled { direction: Some(Right), borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None))
@@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: None, borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: None, borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
@@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Floating(Some(FloatingPaneCoordinates { x: Some(Fixed(10)), y: None, width: Some(Percent(20)), height: None, pinned: None, borderless: Some(false), border_style: None })), false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Floating(Some(FloatingPaneCoordinates { x: Some(Fixed(10)), y: None, width: Some(Percent(20)), height: None, pinned: None, borderless: Some(false), border_style: None })), false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
@@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: Some(Right), borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit]
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: Some(Right), borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false, None), Exit]
@@ -35,5 +35,6 @@ Some(
stdout_message: None,
},
),
None,
),
)
+3
View File
@@ -1223,6 +1223,7 @@ pub enum ActionName {
DismissInfoPopups = 125,
OpenContextMenu = 126,
MoveTabToPosition = 127,
RunInBackground = 128,
}
impl ActionName {
/// String value of the enum field names used in the ProtoBuf definition.
@@ -1356,6 +1357,7 @@ impl ActionName {
ActionName::DismissInfoPopups => "DismissInfoPopups",
ActionName::OpenContextMenu => "OpenContextMenu",
ActionName::MoveTabToPosition => "MoveTabToPosition",
ActionName::RunInBackground => "RunInBackground",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@@ -1486,6 +1488,7 @@ impl ActionName {
"DismissInfoPopups" => Some(Self::DismissInfoPopups),
"OpenContextMenu" => Some(Self::OpenContextMenu),
"MoveTabToPosition" => Some(Self::MoveTabToPosition),
"RunInBackground" => Some(Self::RunInBackground),
_ => None,
}
}
@@ -154,7 +154,7 @@ pub struct RgbColor {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Action {
#[prost(oneof="action::ActionType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 156, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 157, 158, 159, 160, 161, 162")]
#[prost(oneof="action::ActionType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 156, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 157, 158, 159, 160, 161, 162, 163")]
pub action_type: ::core::option::Option<action::ActionType>,
}
/// Nested message and enum types in `Action`.
@@ -486,6 +486,8 @@ pub mod action {
OpenContextMenu(super::OpenContextMenuAction),
#[prost(message, tag="162")]
MoveTabToPosition(super::MoveTabToPositionAction),
#[prost(message, tag="163")]
RunInBackground(super::RunInBackgroundAction),
}
}
// Action message definitions (all 92 variants)
@@ -1116,6 +1118,12 @@ pub struct RunAction {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RunInBackgroundAction {
#[prost(message, optional, tag="1")]
pub command: ::core::option::Option<RunCommandAction>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LaunchOrFocusPluginAction {
#[prost(message, optional, tag="1")]
pub plugin: ::core::option::Option<RunPluginOrAlias>,
@@ -327,6 +327,7 @@ message Action {
DismissInfoPopupsAction dismiss_info_popups = 160;
OpenContextMenuAction open_context_menu = 161;
MoveTabToPositionAction move_tab_to_position = 162;
RunInBackgroundAction run_in_background = 163;
}
}
@@ -615,6 +616,10 @@ message RunAction {
bool no_focus = 3;
}
message RunInBackgroundAction {
RunCommandAction command = 1;
}
message LaunchOrFocusPluginAction {
RunPluginOrAlias plugin = 1;
bool should_float = 2;
+2
View File
@@ -545,6 +545,7 @@ pub enum PtyContext {
GetPaneCwd,
UpdateAndReportCwds,
NotifyCwdFromOsc7,
RunInBackground,
Exit,
}
@@ -705,6 +706,7 @@ pub enum BackgroundJobContext {
ReportSessionInfo,
ReportLayoutInfo,
RunCommand,
RunInBackground,
WebRequest,
ReportPluginList,
ListWebSessions,
+4 -1
View File
@@ -405,6 +405,9 @@ pub enum Action {
near_current_pane: bool,
no_focus: bool,
},
RunInBackground {
command: RunCommandAction,
},
/// Set pane default foreground/background color
SetPaneColor {
pane_id: PaneId,
@@ -2470,7 +2473,7 @@ impl Action {
.as_mut()
.map(|c| c.populate_originating_plugin(originating_plugin));
},
Action::Run { command, .. } => {
Action::Run { command, .. } | Action::RunInBackground { command } => {
command.populate_originating_plugin(originating_plugin);
},
Action::EditFile { payload, .. } => {
@@ -1298,6 +1298,7 @@ impl From<crate::input::actions::Action>
ResizeAction,
ResizeByPaneIdAction,
RunAction,
RunInBackgroundAction,
SaveSessionAction,
ScrollDownAction,
ScrollDownAtAction,
@@ -1760,6 +1761,11 @@ impl From<crate::input::actions::Action>
near_current_pane,
no_focus,
}),
crate::input::actions::Action::RunInBackground { command } => {
ActionType::RunInBackground(RunInBackgroundAction {
command: Some(command.into()),
})
},
crate::input::actions::Action::Detach => ActionType::Detach(DetachAction {}),
crate::input::actions::Action::SetDarkTheme => {
ActionType::SetDarkTheme(SetDarkThemeAction {})
@@ -2725,6 +2731,14 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
near_current_pane: run_action.near_current_pane,
no_focus: run_action.no_focus,
}),
ActionType::RunInBackground(run_action) => {
Ok(crate::input::actions::Action::RunInBackground {
command: run_action
.command
.ok_or_else(|| anyhow!("RunInBackground missing command"))?
.try_into()?,
})
},
ActionType::Detach(_) => Ok(crate::input::actions::Action::Detach),
ActionType::SetDarkTheme(_) => Ok(crate::input::actions::Action::SetDarkTheme),
ActionType::SetLightTheme(_) => Ok(crate::input::actions::Action::SetLightTheme),
@@ -2418,6 +2418,23 @@ fn test_client_messages() {
client_id: Some(100),
is_cli_client: true,
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::RunInBackground {
command: RunCommandAction {
command: PathBuf::from("/path/to/command"),
args: vec!["up".to_owned()],
cwd: Some(PathBuf::from("/tmp")),
direction: None,
hold_on_close: false,
hold_on_start: false,
originating_plugin: None,
use_terminal_title: false,
},
},
terminal_id: Some(1),
client_id: Some(100),
is_cli_client: true,
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::Detach,
terminal_id: Some(1),
+53
View File
@@ -1143,6 +1143,26 @@ impl Action {
Some(node)
},
},
Action::RunInBackground {
command: run_command_action,
} => {
let mut node = KdlNode::new("Run");
let mut node_children = KdlDocument::new();
node.push(run_command_action.command.display().to_string());
for arg in &run_command_action.args {
node.push(arg.clone());
}
let mut background_node = KdlNode::new("background");
background_node.push(KdlValue::Bool(true));
node_children.nodes_mut().push(background_node);
if let Some(cwd) = &run_command_action.cwd {
let mut cwd_node = KdlNode::new("cwd");
cwd_node.push(cwd.display().to_string());
node_children.nodes_mut().push(cwd_node);
}
node.set_children(node_children);
Some(node)
},
Action::Detach => Some(KdlNode::new("Detach")),
Action::SwitchSession {
name,
@@ -2182,6 +2202,19 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
let stacked = command_metadata
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "stacked"))
.unwrap_or(false);
let background = command_metadata
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "background"))
.unwrap_or(false);
if background {
return Ok(Action::RunInBackground {
command: RunCommandAction {
command: PathBuf::from(command),
args,
cwd,
..Default::default()
},
});
}
let run_command_action = RunCommandAction {
command: PathBuf::from(command),
args,
@@ -10364,3 +10397,23 @@ fn config_merge_takes_non_empty_context_menu_sections_from_the_other_config() {
)]
);
}
#[test]
fn run_with_background_parses_and_is_written_back() {
let text = r#"Run "send-scroll" "--direction" "up" { background true; cwd "/tmp"; floating true; name "ignored"; }"#;
let document: KdlDocument = text.parse().unwrap();
let node = document.nodes().first().unwrap();
let action = Action::try_from((node, &Options::default())).unwrap();
let expected = Action::RunInBackground {
command: RunCommandAction {
command: PathBuf::from("send-scroll"),
args: vec!["--direction".to_owned(), "up".to_owned()],
cwd: Some(PathBuf::from("/tmp")),
..Default::default()
},
};
assert_eq!(action, expected);
let written = action.to_kdl().unwrap();
let reparsed = Action::try_from((&written, &Options::default())).unwrap();
assert_eq!(reparsed, expected);
}
+1
View File
@@ -557,6 +557,7 @@ enum ActionName {
DismissInfoPopups = 125;
OpenContextMenu = 126;
MoveTabToPosition = 127;
RunInBackground = 128;
}
message Position {
+33
View File
@@ -672,6 +672,14 @@ impl TryFrom<ProtobufAction> for Action {
},
_ => Err("Wrong payload for Action::Run"),
},
Some(ProtobufActionName::RunInBackground) => match protobuf_action.optional_payload {
Some(OptionalPayload::RunPayload(run_command_action)) => {
Ok(Action::RunInBackground {
command: run_command_action.try_into()?,
})
},
_ => Err("Wrong payload for Action::RunInBackground"),
},
Some(ProtobufActionName::Detach) => match protobuf_action.optional_payload {
Some(_) => Err("Detach should not have a payload"),
None => Ok(Action::Detach),
@@ -1902,6 +1910,15 @@ impl TryFrom<Action> for ProtobufAction {
optional_payload: Some(OptionalPayload::RunPayload(run_command_action)),
})
},
Action::RunInBackground {
command: run_command_action,
} => {
let run_command_action: ProtobufRunCommandAction = run_command_action.try_into()?;
Ok(ProtobufAction {
name: ProtobufActionName::RunInBackground as i32,
optional_payload: Some(OptionalPayload::RunPayload(run_command_action)),
})
},
Action::Detach => Ok(ProtobufAction {
name: ProtobufActionName::Detach as i32,
optional_payload: None,
@@ -3650,6 +3667,22 @@ mod tests {
]
}
#[test]
fn run_in_background_round_trips_over_protobuf() {
let original = Action::RunInBackground {
command: RunCommandAction {
command: PathBuf::from("send-scroll"),
args: vec!["up".to_owned()],
cwd: Some(PathBuf::from("/tmp")),
..Default::default()
},
};
let protobuf: ProtobufAction = original.clone().try_into().unwrap();
assert_eq!(protobuf.name, ProtobufActionName::RunInBackground as i32);
let decoded: Action = protobuf.try_into().unwrap();
assert_eq!(original, decoded);
}
#[test]
fn by_id_actions_keep_their_target_over_protobuf() {
for original in by_id_actions() {