mirror of
https://github.com/zellij-org/zellij.git
synced 2026-10-11 22:50:07 +00:00
Run keybinding improvements (#5701)
* add ORIGIN_PANE_ID to Run commands started from a keybinding * RunInBackground * add pr
This commit is contained in:
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -44,5 +44,6 @@ Some(
|
||||
stdout_message: None,
|
||||
},
|
||||
),
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
@@ -26,5 +26,6 @@ Some(
|
||||
},
|
||||
),
|
||||
false,
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
@@ -49,5 +49,6 @@ Some(
|
||||
},
|
||||
),
|
||||
false,
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
@@ -46,5 +46,6 @@ Some(
|
||||
},
|
||||
),
|
||||
false,
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
@@ -48,5 +48,6 @@ Some(
|
||||
},
|
||||
),
|
||||
false,
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
@@ -44,5 +44,6 @@ Some(
|
||||
},
|
||||
),
|
||||
false,
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
@@ -46,5 +46,6 @@ Some(
|
||||
},
|
||||
),
|
||||
false,
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
@@ -46,5 +46,6 @@ Some(
|
||||
},
|
||||
),
|
||||
false,
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
@@ -48,5 +48,6 @@ Some(
|
||||
},
|
||||
),
|
||||
false,
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
@@ -39,5 +39,6 @@ Some(
|
||||
},
|
||||
),
|
||||
false,
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
@@ -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
@@ -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));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -2722,6 +2722,7 @@ impl Tab {
|
||||
client_id_or_tab_index,
|
||||
completion_tx,
|
||||
false, // set_blocking
|
||||
None,
|
||||
);
|
||||
self.senders
|
||||
.send_to_pty(instruction)
|
||||
|
||||
+1
-1
@@ -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]
|
||||
|
||||
+1
-1
@@ -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]
|
||||
|
||||
+1
-1
@@ -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]
|
||||
|
||||
+1
@@ -34,5 +34,6 @@ Some(
|
||||
stdout_message: None,
|
||||
},
|
||||
),
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
+1
-1
@@ -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))
|
||||
|
||||
+1
-1
@@ -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]
|
||||
|
||||
+1
-1
@@ -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]
|
||||
|
||||
+1
-1
@@ -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]
|
||||
|
||||
+1
@@ -35,5 +35,6 @@ Some(
|
||||
stdout_message: None,
|
||||
},
|
||||
),
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -557,6 +557,7 @@ enum ActionName {
|
||||
DismissInfoPopups = 125;
|
||||
OpenContextMenu = 126;
|
||||
MoveTabToPosition = 127;
|
||||
RunInBackground = 128;
|
||||
}
|
||||
|
||||
message Position {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user