mirror of
https://github.com/zellij-org/zellij.git
synced 2024-12-23 17:22:28 +03:00
feat(command-panes): allow to start suspended (#1887)
* feat(command-panes): allow panes to start suspended * style(fmt): remove unused code * style(fmt): rustfmt
This commit is contained in:
parent
6d29c6951e
commit
abc700fc4d
@ -26,6 +26,7 @@ fn main() {
|
||||
floating,
|
||||
name,
|
||||
close_on_exit,
|
||||
start_suspended,
|
||||
})) = opts.command
|
||||
{
|
||||
let command_cli_action = CliAction::NewPane {
|
||||
@ -35,6 +36,7 @@ fn main() {
|
||||
floating,
|
||||
name,
|
||||
close_on_exit,
|
||||
start_suspended,
|
||||
};
|
||||
commands::send_action_to_session(command_cli_action, opts.session);
|
||||
std::process::exit(0);
|
||||
|
@ -1914,6 +1914,25 @@ pub fn send_command_through_the_cli() {
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Initial run of suspended command",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("<Ctrl-c>")
|
||||
&& remote_terminal.cursor_position_is(0, 0)
|
||||
// cursor does not appear in
|
||||
// suspend_start panes
|
||||
{
|
||||
remote_terminal.send_key(&SPACE); // run script - here we use SPACE
|
||||
// instead of the default ENTER because
|
||||
// sending ENTER over SSH can be a little
|
||||
// problematic (read: I couldn't get it
|
||||
// to pass consistently)
|
||||
step_is_complete = true
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for command to run",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
|
@ -346,7 +346,8 @@ impl RemoteTerminal {
|
||||
let mut channel = self.channel.lock().unwrap();
|
||||
channel
|
||||
.write_all(
|
||||
format!("{} run -- \"{}\"\n", ZELLIJ_EXECUTABLE_LOCATION, command).as_bytes(),
|
||||
// note that this is run with the -s flag that suspends the command on startup
|
||||
format!("{} run -s -- \"{}\"\n", ZELLIJ_EXECUTABLE_LOCATION, command).as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
|
@ -1,14 +1,14 @@
|
||||
---
|
||||
source: src/tests/e2e/cases.rs
|
||||
assertion_line: 1968
|
||||
assertion_line: 1998
|
||||
expression: last_snapshot
|
||||
---
|
||||
Zellij (e2e-test) Tab #1
|
||||
┌ Pane #1 ─────────────────────────────────────────────────┐┌ /usr/src/zellij/fixtures/append-echo-script.sh ──────────┐
|
||||
│$ /usr/src/zellij/x86_64-unknown-linux-musl/release/zellij││foo │
|
||||
│ run -- "/usr/src/zellij/fixtures/append-echo-script.sh" ││foo │
|
||||
│$ ││█ │
|
||||
│ ││ │
|
||||
│ run -s -- "/usr/src/zellij/fixtures/append-echo-script.sh││foo │
|
||||
│" ││█ │
|
||||
│$ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
|
@ -280,6 +280,7 @@ fn spawn_terminal(
|
||||
args,
|
||||
cwd: None,
|
||||
hold_on_close: false,
|
||||
hold_on_start: false,
|
||||
}
|
||||
},
|
||||
TerminalAction::RunCommand(command) => command,
|
||||
@ -381,6 +382,10 @@ pub trait ServerOsApi: Send + Sync {
|
||||
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status
|
||||
default_editor: Option<PathBuf>,
|
||||
) -> Result<(u32, RawFd, RawFd), SpawnTerminalError>;
|
||||
// reserves a terminal id without actually opening a terminal
|
||||
fn reserve_terminal_id(&self) -> Result<u32, SpawnTerminalError> {
|
||||
unimplemented!()
|
||||
}
|
||||
/// Read bytes from the standard output of the virtual terminal referred to by `fd`.
|
||||
fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
|
||||
/// Creates an `AsyncReader` that can be used to read from `fd` in an async context
|
||||
@ -484,6 +489,35 @@ impl ServerOsApi for ServerOsInputOutput {
|
||||
None => Err(SpawnTerminalError::NoMoreTerminalIds),
|
||||
}
|
||||
}
|
||||
fn reserve_terminal_id(&self) -> Result<u32, SpawnTerminalError> {
|
||||
let mut terminal_id = None;
|
||||
{
|
||||
let current_ids: HashSet<u32> = self
|
||||
.terminal_id_to_raw_fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.copied()
|
||||
.collect();
|
||||
for i in 0..u32::MAX {
|
||||
let i = i as u32;
|
||||
if !current_ids.contains(&i) {
|
||||
terminal_id = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
match terminal_id {
|
||||
Some(terminal_id) => {
|
||||
self.terminal_id_to_raw_fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(terminal_id, None);
|
||||
Ok(terminal_id)
|
||||
},
|
||||
None => Err(SpawnTerminalError::NoMoreTerminalIds),
|
||||
}
|
||||
}
|
||||
fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
unistd::read(fd, buf)
|
||||
}
|
||||
|
@ -156,11 +156,12 @@ impl FloatingPanes {
|
||||
&mut self,
|
||||
pane_id: PaneId,
|
||||
exit_status: Option<i32>,
|
||||
is_first_run: bool,
|
||||
run_command: RunCommand,
|
||||
) {
|
||||
self.panes
|
||||
.get_mut(&pane_id)
|
||||
.map(|p| p.hold(exit_status, run_command));
|
||||
.map(|p| p.hold(exit_status, is_first_run, run_command));
|
||||
}
|
||||
pub fn get(&self, pane_id: &PaneId) -> Option<&Box<dyn Pane>> {
|
||||
self.panes.get(pane_id)
|
||||
|
@ -1533,6 +1533,7 @@ impl Grid {
|
||||
self.sixel_scrolling = false;
|
||||
self.mouse_mode = MouseMode::NoEncoding;
|
||||
self.mouse_tracking = MouseTracking::Off;
|
||||
self.cursor_is_hidden = false;
|
||||
if let Some(images_to_reap) = self.sixel_grid.clear() {
|
||||
self.sixel_grid.reap_images(images_to_reap);
|
||||
}
|
||||
|
@ -3,7 +3,12 @@ use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::ops::{Index, IndexMut};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use zellij_utils::{data::PaletteColor, vte::ParamsIter};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use zellij_utils::input::command::RunCommand;
|
||||
use zellij_utils::{
|
||||
data::{PaletteColor, Style},
|
||||
vte::ParamsIter,
|
||||
};
|
||||
|
||||
use crate::panes::alacritty_functions::parse_sgr_color;
|
||||
|
||||
@ -736,3 +741,113 @@ impl ::std::fmt::Debug for TerminalCharacter {
|
||||
write!(f, "{}", self.character)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_first_run_banner(
|
||||
columns: usize,
|
||||
rows: usize,
|
||||
style: &Style,
|
||||
run_command: Option<&RunCommand>,
|
||||
) -> String {
|
||||
let middle_row = rows / 2;
|
||||
let middle_column = columns / 2;
|
||||
match run_command {
|
||||
Some(run_command) => {
|
||||
let bold_text = RESET_STYLES.bold(Some(AnsiCode::On));
|
||||
let command_color_text = RESET_STYLES
|
||||
.foreground(Some(AnsiCode::from(style.colors.green)))
|
||||
.bold(Some(AnsiCode::On));
|
||||
let waiting_to_run_text = "Waiting to run: ";
|
||||
let command_text = run_command.to_string();
|
||||
let waiting_to_run_text_width = waiting_to_run_text.width() + command_text.width();
|
||||
let column_start_postion = middle_column.saturating_sub(waiting_to_run_text_width / 2);
|
||||
let waiting_to_run_line = format!(
|
||||
"\u{1b}[{};{}H{}{}{}{}{}",
|
||||
middle_row,
|
||||
column_start_postion,
|
||||
bold_text,
|
||||
waiting_to_run_text,
|
||||
command_color_text,
|
||||
command_text,
|
||||
RESET_STYLES
|
||||
);
|
||||
|
||||
let controls_bare_text_first_part = "<";
|
||||
let enter_bare_text = "ENTER";
|
||||
let controls_bare_text_second_part = "> to run, <";
|
||||
let ctrl_c_bare_text = "Ctrl-c";
|
||||
let controls_bare_text_third_part = "> to exit";
|
||||
let controls_color = RESET_STYLES
|
||||
.foreground(Some(AnsiCode::from(style.colors.orange)))
|
||||
.bold(Some(AnsiCode::On));
|
||||
let controls_line_length = controls_bare_text_first_part.len()
|
||||
+ enter_bare_text.len()
|
||||
+ controls_bare_text_second_part.len()
|
||||
+ ctrl_c_bare_text.len()
|
||||
+ controls_bare_text_third_part.len();
|
||||
let controls_column_start_position =
|
||||
middle_column.saturating_sub(controls_line_length / 2);
|
||||
let controls_line = format!(
|
||||
"\u{1b}[{};{}H{}<{}{}{}{}> to run, <{}{}{}{}> to exit",
|
||||
middle_row + 2,
|
||||
controls_column_start_position,
|
||||
bold_text,
|
||||
controls_color,
|
||||
enter_bare_text,
|
||||
RESET_STYLES,
|
||||
bold_text,
|
||||
controls_color,
|
||||
ctrl_c_bare_text,
|
||||
RESET_STYLES,
|
||||
bold_text
|
||||
);
|
||||
format!(
|
||||
"\u{1b}[?25l{}{}{}{}",
|
||||
RESET_STYLES, waiting_to_run_line, controls_line, RESET_STYLES
|
||||
)
|
||||
},
|
||||
None => {
|
||||
let bare_text = format!("Waiting to start...");
|
||||
let bare_text_width = bare_text.width();
|
||||
let column_start_postion = middle_column.saturating_sub(bare_text_width / 2);
|
||||
let bold_text = RESET_STYLES.bold(Some(AnsiCode::On));
|
||||
let waiting_to_run_line = format!(
|
||||
"\u{1b}[?25l\u{1b}[{};{}H{}{}{}",
|
||||
middle_row, column_start_postion, bold_text, bare_text, RESET_STYLES
|
||||
);
|
||||
|
||||
let controls_bare_text_first_part = "<";
|
||||
let enter_bare_text = "ENTER";
|
||||
let controls_bare_text_second_part = "> to run, <";
|
||||
let ctrl_c_bare_text = "Ctrl-c";
|
||||
let controls_bare_text_third_part = "> to exit";
|
||||
let controls_color = RESET_STYLES
|
||||
.foreground(Some(AnsiCode::from(style.colors.orange)))
|
||||
.bold(Some(AnsiCode::On));
|
||||
let controls_line_length = controls_bare_text_first_part.len()
|
||||
+ enter_bare_text.len()
|
||||
+ controls_bare_text_second_part.len()
|
||||
+ ctrl_c_bare_text.len()
|
||||
+ controls_bare_text_third_part.len();
|
||||
let controls_column_start_position =
|
||||
middle_column.saturating_sub(controls_line_length / 2);
|
||||
let controls_line = format!(
|
||||
"\u{1b}[{};{}H{}<{}{}{}{}> to run, <{}{}{}{}> to exit",
|
||||
middle_row + 2,
|
||||
controls_column_start_position,
|
||||
bold_text,
|
||||
controls_color,
|
||||
enter_bare_text,
|
||||
RESET_STYLES,
|
||||
bold_text,
|
||||
controls_color,
|
||||
ctrl_c_bare_text,
|
||||
RESET_STYLES,
|
||||
bold_text
|
||||
);
|
||||
format!(
|
||||
"\u{1b}[?25l{}{}{}{}",
|
||||
RESET_STYLES, waiting_to_run_line, controls_line, RESET_STYLES
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::output::{CharacterChunk, SixelImageChunk};
|
||||
use crate::panes::sixel::SixelImageStore;
|
||||
use crate::panes::{
|
||||
grid::Grid,
|
||||
terminal_character::{TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
|
||||
terminal_character::{render_first_run_banner, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
|
||||
};
|
||||
use crate::panes::{AnsiCode, LinkHandler};
|
||||
use crate::pty::VteBytes;
|
||||
@ -83,6 +83,8 @@ pub enum PaneId {
|
||||
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
|
||||
}
|
||||
|
||||
type IsFirstRun = bool;
|
||||
|
||||
// FIXME: This should hold an os_api handle so that terminal panes can set their own size via FD in
|
||||
// their `reflow_lines()` method. Drop a Box<dyn ServerOsApi> in here somewhere.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -104,8 +106,10 @@ pub struct TerminalPane {
|
||||
borderless: bool,
|
||||
fake_cursor_locations: HashSet<(usize, usize)>, // (x, y) - these hold a record of previous fake cursors which we need to clear on render
|
||||
search_term: String,
|
||||
is_held: Option<(Option<i32>, RunCommand)>, // a "held" pane means that its command has exited and its waiting for a
|
||||
// possible user instruction to be re-run
|
||||
is_held: Option<(Option<i32>, IsFirstRun, RunCommand)>, // a "held" pane means that its command has either exited and the pane is waiting for a
|
||||
// possible user instruction to be re-run, or that the command has not yet been run
|
||||
banner: Option<String>, // a banner to be rendered inside this TerminalPane, used for panes
|
||||
// held on startup and can possibly be used to display some errors
|
||||
}
|
||||
|
||||
impl Pane for TerminalPane {
|
||||
@ -170,13 +174,14 @@ impl Pane for TerminalPane {
|
||||
// needs to be adjusted.
|
||||
// here we match against those cases - if need be, we adjust the input and if not
|
||||
// we send back the original input
|
||||
if let Some((_exit_status, run_command)) = &self.is_held {
|
||||
if let Some((_exit_status, _is_first_run, run_command)) = &self.is_held {
|
||||
match input_bytes.as_slice() {
|
||||
ENTER_CARRIAGE_RETURN | ENTER_NEWLINE | SPACE => {
|
||||
let run_command = run_command.clone();
|
||||
self.is_held = None;
|
||||
self.grid.reset_terminal_state();
|
||||
self.set_should_render(true);
|
||||
self.remove_banner();
|
||||
Some(AdjustedInput::ReRunCommandInThisPane(run_command))
|
||||
},
|
||||
CTRL_C => Some(AdjustedInput::CloseThisPane),
|
||||
@ -395,8 +400,12 @@ impl Pane for TerminalPane {
|
||||
pane_title,
|
||||
frame_params,
|
||||
);
|
||||
if let Some((exit_status, _run_command)) = &self.is_held {
|
||||
frame.add_exit_status(exit_status.as_ref().copied());
|
||||
if let Some((exit_status, is_first_run, _run_command)) = &self.is_held {
|
||||
if *is_first_run {
|
||||
frame.indicate_first_run();
|
||||
} else {
|
||||
frame.add_exit_status(exit_status.as_ref().copied());
|
||||
}
|
||||
}
|
||||
|
||||
let res = match self.frame.get(&client_id) {
|
||||
@ -701,8 +710,11 @@ impl Pane for TerminalPane {
|
||||
fn is_alternate_mode_active(&self) -> bool {
|
||||
self.grid.is_alternate_mode_active()
|
||||
}
|
||||
fn hold(&mut self, exit_status: Option<i32>, run_command: RunCommand) {
|
||||
self.is_held = Some((exit_status, run_command));
|
||||
fn hold(&mut self, exit_status: Option<i32>, is_first_run: bool, run_command: RunCommand) {
|
||||
self.is_held = Some((exit_status, is_first_run, run_command));
|
||||
if is_first_run {
|
||||
self.render_first_run_banner();
|
||||
}
|
||||
self.set_should_render(true);
|
||||
}
|
||||
}
|
||||
@ -752,6 +764,7 @@ impl TerminalPane {
|
||||
fake_cursor_locations: HashSet::new(),
|
||||
search_term: String::new(),
|
||||
is_held: None,
|
||||
banner: None,
|
||||
}
|
||||
}
|
||||
pub fn get_x(&self) -> usize {
|
||||
@ -782,6 +795,10 @@ impl TerminalPane {
|
||||
let rows = self.get_content_rows();
|
||||
let cols = self.get_content_columns();
|
||||
self.grid.change_size(rows, cols);
|
||||
if self.banner.is_some() {
|
||||
self.grid.reset_terminal_state();
|
||||
self.render_first_run_banner();
|
||||
}
|
||||
self.set_should_render(true);
|
||||
}
|
||||
pub fn read_buffer_as_lines(&self) -> Vec<Vec<TerminalCharacter>> {
|
||||
@ -791,6 +808,25 @@ impl TerminalPane {
|
||||
// (x, y)
|
||||
self.grid.cursor_coordinates()
|
||||
}
|
||||
fn render_first_run_banner(&mut self) {
|
||||
let columns = self.get_content_columns();
|
||||
let rows = self.get_content_rows();
|
||||
let banner = match &self.is_held {
|
||||
Some((_exit_status, _is_first_run, run_command)) => {
|
||||
render_first_run_banner(columns, rows, &self.style, Some(run_command))
|
||||
},
|
||||
None => render_first_run_banner(columns, rows, &self.style, None),
|
||||
};
|
||||
self.banner = Some(banner.clone());
|
||||
self.handle_pty_bytes(banner.as_bytes().to_vec());
|
||||
}
|
||||
fn remove_banner(&mut self) {
|
||||
if self.banner.is_some() {
|
||||
self.grid.reset_terminal_state();
|
||||
self.set_should_render(true);
|
||||
self.banner = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1010,11 +1010,12 @@ impl TiledPanes {
|
||||
&mut self,
|
||||
pane_id: PaneId,
|
||||
exit_status: Option<i32>,
|
||||
is_first_run: bool,
|
||||
run_command: RunCommand,
|
||||
) {
|
||||
self.panes
|
||||
.get_mut(&pane_id)
|
||||
.map(|p| p.hold(exit_status, run_command));
|
||||
.map(|p| p.hold(exit_status, is_first_run, run_command));
|
||||
}
|
||||
pub fn panes_to_hide_contains(&self, pane_id: PaneId) -> bool {
|
||||
self.panes_to_hide.contains(&pane_id)
|
||||
|
@ -108,25 +108,29 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
|
||||
_ => (false, None, name),
|
||||
};
|
||||
match pty.spawn_terminal(terminal_action, client_or_tab_index) {
|
||||
Ok(pid) => {
|
||||
Ok((pid, starts_held)) => {
|
||||
let hold_for_command = if starts_held { run_command } else { None };
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::NewPane(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
should_float,
|
||||
hold_for_command,
|
||||
client_or_tab_index,
|
||||
))
|
||||
.with_context(err_context)?;
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(pid)) => {
|
||||
if hold_on_close {
|
||||
let hold_for_command = None; // we do not hold an "error" pane
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::NewPane(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
should_float,
|
||||
hold_for_command,
|
||||
client_or_tab_index,
|
||||
))
|
||||
.with_context(err_context)?;
|
||||
@ -154,7 +158,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
|
||||
Some(TerminalAction::OpenFile(temp_file, line_number)),
|
||||
ClientOrTabIndex::ClientId(client_id),
|
||||
) {
|
||||
Ok(pid) => {
|
||||
Ok((pid, _starts_held)) => {
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::OpenInPlaceEditor(
|
||||
@ -178,23 +182,27 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
|
||||
_ => (false, None, name),
|
||||
};
|
||||
match pty.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) {
|
||||
Ok(pid) => {
|
||||
Ok((pid, starts_held)) => {
|
||||
let hold_for_command = if starts_held { run_command } else { None };
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::VerticalSplit(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
hold_for_command,
|
||||
client_id,
|
||||
))
|
||||
.with_context(err_context)?;
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(pid)) => {
|
||||
if hold_on_close {
|
||||
let hold_for_command = None; // error panes are never held
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::VerticalSplit(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
hold_for_command,
|
||||
client_id,
|
||||
))
|
||||
.with_context(err_context)?;
|
||||
@ -238,23 +246,27 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
|
||||
_ => (false, None, name),
|
||||
};
|
||||
match pty.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) {
|
||||
Ok(pid) => {
|
||||
Ok((pid, starts_held)) => {
|
||||
let hold_for_command = if starts_held { run_command } else { None };
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HorizontalSplit(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
hold_for_command,
|
||||
client_id,
|
||||
))
|
||||
.with_context(err_context)?;
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(pid)) => {
|
||||
if hold_on_close {
|
||||
let hold_for_command = None; // error panes are never held
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HorizontalSplit(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
hold_for_command,
|
||||
client_id,
|
||||
))
|
||||
.with_context(err_context)?;
|
||||
@ -391,6 +403,7 @@ impl Pty {
|
||||
command: PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")),
|
||||
cwd, // note: this might also be filled by the calling function, eg. spawn_terminal
|
||||
hold_on_close: false,
|
||||
hold_on_start: false,
|
||||
})
|
||||
}
|
||||
fn fill_cwd(&self, terminal_action: &mut TerminalAction, client_id: ClientId) {
|
||||
@ -416,7 +429,8 @@ impl Pty {
|
||||
&mut self,
|
||||
terminal_action: Option<TerminalAction>,
|
||||
client_or_tab_index: ClientOrTabIndex,
|
||||
) -> Result<u32, SpawnTerminalError> {
|
||||
) -> Result<(u32, bool), SpawnTerminalError> {
|
||||
// bool is starts_held
|
||||
// returns the terminal id
|
||||
let terminal_action = match client_or_tab_index {
|
||||
ClientOrTabIndex::ClientId(client_id) => {
|
||||
@ -429,10 +443,20 @@ impl Pty {
|
||||
terminal_action.unwrap_or_else(|| self.get_default_terminal(None))
|
||||
},
|
||||
};
|
||||
let hold_on_close = match &terminal_action {
|
||||
TerminalAction::RunCommand(run_command) => run_command.hold_on_close,
|
||||
_ => false,
|
||||
let (hold_on_start, hold_on_close) = match &terminal_action {
|
||||
TerminalAction::RunCommand(run_command) => {
|
||||
(run_command.hold_on_start, run_command.hold_on_close)
|
||||
},
|
||||
_ => (false, false),
|
||||
};
|
||||
|
||||
if hold_on_start {
|
||||
// we don't actually open a terminal in this case, just wait for the user to run it
|
||||
let starts_held = hold_on_start;
|
||||
let terminal_id = self.bus.os_input.as_mut().unwrap().reserve_terminal_id()?;
|
||||
return Ok((terminal_id, starts_held));
|
||||
}
|
||||
|
||||
let quit_cb = Box::new({
|
||||
let senders = self.bus.senders.clone();
|
||||
move |pane_id, exit_status, command| {
|
||||
@ -477,7 +501,8 @@ impl Pty {
|
||||
|
||||
self.task_handles.insert(terminal_id, terminal_bytes);
|
||||
self.id_to_child_pid.insert(terminal_id, child_fd);
|
||||
Ok(terminal_id)
|
||||
let starts_held = false;
|
||||
Ok((terminal_id, starts_held))
|
||||
}
|
||||
pub fn spawn_terminals_for_layout(
|
||||
&mut self,
|
||||
@ -489,10 +514,15 @@ impl Pty {
|
||||
let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal(None));
|
||||
self.fill_cwd(&mut default_shell, client_id);
|
||||
let extracted_run_instructions = layout.extract_run_instructions();
|
||||
let mut new_pane_pids: Vec<(u32, Option<RunCommand>, Result<RawFd, SpawnTerminalError>)> =
|
||||
vec![]; // (terminal_id,
|
||||
// run_command
|
||||
// file_descriptor)
|
||||
let mut new_pane_pids: Vec<(
|
||||
u32,
|
||||
bool,
|
||||
Option<RunCommand>,
|
||||
Result<RawFd, SpawnTerminalError>,
|
||||
)> = vec![]; // (terminal_id,
|
||||
// starts_held,
|
||||
// run_command,
|
||||
// file_descriptor)
|
||||
for run_instruction in extracted_run_instructions {
|
||||
let quit_cb = Box::new({
|
||||
let senders = self.bus.senders.clone();
|
||||
@ -502,6 +532,7 @@ impl Pty {
|
||||
});
|
||||
match run_instruction {
|
||||
Some(Run::Command(command)) => {
|
||||
let starts_held = command.hold_on_start;
|
||||
let hold_on_close = command.hold_on_close;
|
||||
let quit_cb = Box::new({
|
||||
let senders = self.bus.senders.clone();
|
||||
@ -520,34 +551,56 @@ impl Pty {
|
||||
}
|
||||
});
|
||||
let cmd = TerminalAction::RunCommand(command.clone());
|
||||
match self
|
||||
.bus
|
||||
.os_input
|
||||
.as_mut()
|
||||
.with_context(err_context)?
|
||||
.spawn_terminal(cmd, quit_cb, self.default_editor.clone())
|
||||
{
|
||||
Ok((terminal_id, pid_primary, child_fd)) => {
|
||||
self.id_to_child_pid.insert(terminal_id, child_fd);
|
||||
new_pane_pids.push((
|
||||
terminal_id,
|
||||
Some(command.clone()),
|
||||
Ok(pid_primary),
|
||||
));
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)) => {
|
||||
new_pane_pids.push((
|
||||
terminal_id,
|
||||
Some(command.clone()),
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)),
|
||||
));
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to spawn terminal: {}", e);
|
||||
},
|
||||
if starts_held {
|
||||
// we don't actually open a terminal in this case, just wait for the user to run it
|
||||
match self.bus.os_input.as_mut().unwrap().reserve_terminal_id() {
|
||||
Ok(terminal_id) => {
|
||||
new_pane_pids.push((
|
||||
terminal_id,
|
||||
starts_held,
|
||||
Some(command.clone()),
|
||||
Ok(terminal_id as i32), // this is not actually correct but gets
|
||||
// stripped later
|
||||
));
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to spawn terminal: {}", e);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
match self
|
||||
.bus
|
||||
.os_input
|
||||
.as_mut()
|
||||
.with_context(err_context)?
|
||||
.spawn_terminal(cmd, quit_cb, self.default_editor.clone())
|
||||
{
|
||||
Ok((terminal_id, pid_primary, child_fd)) => {
|
||||
self.id_to_child_pid.insert(terminal_id, child_fd);
|
||||
new_pane_pids.push((
|
||||
terminal_id,
|
||||
starts_held,
|
||||
Some(command.clone()),
|
||||
Ok(pid_primary),
|
||||
));
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)) => {
|
||||
let starts_held = false; // we do not hold error panes
|
||||
new_pane_pids.push((
|
||||
terminal_id,
|
||||
starts_held,
|
||||
Some(command.clone()),
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)),
|
||||
));
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to spawn terminal: {}", e);
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(Run::Cwd(cwd)) => {
|
||||
let starts_held = false; // we do not hold Cwd panes
|
||||
let shell = self.get_default_terminal(Some(cwd));
|
||||
match self
|
||||
.bus
|
||||
@ -558,11 +611,12 @@ impl Pty {
|
||||
{
|
||||
Ok((terminal_id, pid_primary, child_fd)) => {
|
||||
self.id_to_child_pid.insert(terminal_id, child_fd);
|
||||
new_pane_pids.push((terminal_id, None, Ok(pid_primary)));
|
||||
new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)) => {
|
||||
new_pane_pids.push((
|
||||
terminal_id,
|
||||
starts_held,
|
||||
None,
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)),
|
||||
));
|
||||
@ -573,6 +627,7 @@ impl Pty {
|
||||
}
|
||||
},
|
||||
Some(Run::EditFile(path_to_file, line_number)) => {
|
||||
let starts_held = false; // we do not hold edit panes (for now?)
|
||||
match self
|
||||
.bus
|
||||
.os_input
|
||||
@ -585,11 +640,13 @@ impl Pty {
|
||||
) {
|
||||
Ok((terminal_id, pid_primary, child_fd)) => {
|
||||
self.id_to_child_pid.insert(terminal_id, child_fd);
|
||||
new_pane_pids.push((terminal_id, None, Ok(pid_primary)));
|
||||
new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)) => {
|
||||
let starts_held = false; // we do not hold error panes
|
||||
new_pane_pids.push((
|
||||
terminal_id,
|
||||
starts_held,
|
||||
None,
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)),
|
||||
));
|
||||
@ -600,6 +657,7 @@ impl Pty {
|
||||
}
|
||||
},
|
||||
None => {
|
||||
let starts_held = false;
|
||||
match self
|
||||
.bus
|
||||
.os_input
|
||||
@ -609,11 +667,12 @@ impl Pty {
|
||||
{
|
||||
Ok((terminal_id, pid_primary, child_fd)) => {
|
||||
self.id_to_child_pid.insert(terminal_id, child_fd);
|
||||
new_pane_pids.push((terminal_id, None, Ok(pid_primary)));
|
||||
new_pane_pids.push((terminal_id, starts_held, None, Ok(pid_primary)));
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)) => {
|
||||
new_pane_pids.push((
|
||||
terminal_id,
|
||||
starts_held,
|
||||
None,
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)),
|
||||
));
|
||||
@ -627,10 +686,17 @@ impl Pty {
|
||||
Some(Run::Plugin(_)) => {},
|
||||
}
|
||||
}
|
||||
let new_tab_pane_ids: Vec<u32> = new_pane_pids
|
||||
// Option<RunCommand> should only be Some if the pane starts held
|
||||
let new_tab_pane_ids: Vec<(u32, Option<RunCommand>)> = new_pane_pids
|
||||
.iter()
|
||||
.map(|(terminal_id, _, _)| *terminal_id)
|
||||
.collect::<Vec<u32>>();
|
||||
.map(|(terminal_id, starts_held, run_command, _)| {
|
||||
if *starts_held {
|
||||
(*terminal_id, run_command.clone())
|
||||
} else {
|
||||
(*terminal_id, None)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
self.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::NewTab(
|
||||
@ -639,7 +705,11 @@ impl Pty {
|
||||
client_id,
|
||||
))
|
||||
.with_context(err_context)?;
|
||||
for (terminal_id, run_command, pid_primary) in new_pane_pids {
|
||||
for (terminal_id, starts_held, run_command, pid_primary) in new_pane_pids {
|
||||
if starts_held {
|
||||
// we do not run a command or start listening for bytes on held panes
|
||||
continue;
|
||||
}
|
||||
match pid_primary {
|
||||
Ok(pid_primary) => {
|
||||
let terminal_bytes = task::spawn({
|
||||
@ -744,16 +814,21 @@ impl Pty {
|
||||
let _ = self.task_handles.remove(&id); // if all is well, this shouldn't be here
|
||||
let _ = self.id_to_child_pid.remove(&id); // if all is wlel, this shouldn't be here
|
||||
|
||||
let hold_on_close = run_command.hold_on_close;
|
||||
let quit_cb = Box::new({
|
||||
let senders = self.bus.senders.clone();
|
||||
move |pane_id, exit_status, command| {
|
||||
// we only re-run held panes, so we'll never close them from Pty
|
||||
let _ = senders.send_to_screen(ScreenInstruction::HoldPane(
|
||||
pane_id,
|
||||
exit_status,
|
||||
command,
|
||||
None,
|
||||
));
|
||||
if hold_on_close {
|
||||
let _ = senders.send_to_screen(ScreenInstruction::HoldPane(
|
||||
pane_id,
|
||||
exit_status,
|
||||
command,
|
||||
None,
|
||||
));
|
||||
} else {
|
||||
let _ =
|
||||
senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
|
||||
}
|
||||
}
|
||||
});
|
||||
let (pid_primary, child_fd): (RawFd, RawFd) = self
|
||||
|
@ -112,19 +112,27 @@ macro_rules! active_tab_and_connected_client_id {
|
||||
};
|
||||
}
|
||||
|
||||
type InitialTitle = String;
|
||||
type ShouldFloat = bool;
|
||||
type HoldForCommand = Option<RunCommand>;
|
||||
|
||||
/// Instructions that can be sent to the [`Screen`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ScreenInstruction {
|
||||
PtyBytes(u32, VteBytes),
|
||||
Render,
|
||||
NewPane(PaneId, Option<String>, Option<bool>, ClientOrTabIndex), // String is initial title,
|
||||
// bool (if Some) is
|
||||
// should_float
|
||||
NewPane(
|
||||
PaneId,
|
||||
Option<InitialTitle>,
|
||||
Option<ShouldFloat>,
|
||||
HoldForCommand,
|
||||
ClientOrTabIndex,
|
||||
),
|
||||
OpenInPlaceEditor(PaneId, ClientId),
|
||||
TogglePaneEmbedOrFloating(ClientId),
|
||||
ToggleFloatingPanes(ClientId, Option<TerminalAction>),
|
||||
HorizontalSplit(PaneId, Option<String>, ClientId), // String is initial title
|
||||
VerticalSplit(PaneId, Option<String>, ClientId), // String is initial title
|
||||
HorizontalSplit(PaneId, Option<InitialTitle>, HoldForCommand, ClientId),
|
||||
VerticalSplit(PaneId, Option<InitialTitle>, HoldForCommand, ClientId),
|
||||
WriteCharacter(Vec<u8>, ClientId),
|
||||
ResizeLeft(ClientId),
|
||||
ResizeRight(ClientId),
|
||||
@ -167,7 +175,7 @@ pub enum ScreenInstruction {
|
||||
HoldPane(PaneId, Option<i32>, RunCommand, Option<ClientId>), // Option<i32> is the exit status
|
||||
UpdatePaneName(Vec<u8>, ClientId),
|
||||
UndoRenamePane(ClientId),
|
||||
NewTab(PaneLayout, Vec<u32>, ClientId),
|
||||
NewTab(PaneLayout, Vec<(u32, HoldForCommand)>, ClientId),
|
||||
SwitchTabNext(ClientId),
|
||||
SwitchTabPrev(ClientId),
|
||||
ToggleActiveSyncTab(ClientId),
|
||||
@ -811,7 +819,7 @@ impl Screen {
|
||||
pub fn new_tab(
|
||||
&mut self,
|
||||
layout: PaneLayout,
|
||||
new_ids: Vec<u32>,
|
||||
new_ids: Vec<(u32, HoldForCommand)>,
|
||||
client_id: ClientId,
|
||||
) -> Result<()> {
|
||||
let client_id = if self.get_active_tab(client_id).is_some() {
|
||||
@ -1227,6 +1235,7 @@ pub(crate) fn screen_thread_main(
|
||||
pid,
|
||||
initial_pane_title,
|
||||
should_float,
|
||||
hold_for_command,
|
||||
client_or_tab_index,
|
||||
) => {
|
||||
match client_or_tab_index {
|
||||
@ -1237,10 +1246,27 @@ pub(crate) fn screen_thread_main(
|
||||
should_float,
|
||||
Some(client_id)),
|
||||
?);
|
||||
if let Some(hold_for_command) = hold_for_command {
|
||||
let is_first_run = true;
|
||||
active_tab_and_connected_client_id!(
|
||||
screen,
|
||||
client_id,
|
||||
|tab: &mut Tab, _client_id: ClientId| tab.hold_pane(
|
||||
pid,
|
||||
None,
|
||||
is_first_run,
|
||||
hold_for_command
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
ClientOrTabIndex::TabIndex(tab_index) => {
|
||||
if let Some(active_tab) = screen.tabs.get_mut(&tab_index) {
|
||||
active_tab.new_pane(pid, initial_pane_title, should_float, None)?;
|
||||
if let Some(hold_for_command) = hold_for_command {
|
||||
let is_first_run = true;
|
||||
active_tab.hold_pane(pid, None, is_first_run, hold_for_command);
|
||||
}
|
||||
} else {
|
||||
log::error!("Tab index not found: {:?}", tab_index);
|
||||
}
|
||||
@ -1275,24 +1301,60 @@ pub(crate) fn screen_thread_main(
|
||||
|
||||
screen.render()?;
|
||||
},
|
||||
ScreenInstruction::HorizontalSplit(pid, initial_pane_title, client_id) => {
|
||||
ScreenInstruction::HorizontalSplit(
|
||||
pid,
|
||||
initial_pane_title,
|
||||
hold_for_command,
|
||||
client_id,
|
||||
) => {
|
||||
active_tab_and_connected_client_id!(
|
||||
screen,
|
||||
client_id,
|
||||
|tab: &mut Tab, client_id: ClientId| tab.horizontal_split(pid, initial_pane_title, client_id),
|
||||
?
|
||||
);
|
||||
if let Some(hold_for_command) = hold_for_command {
|
||||
let is_first_run = true;
|
||||
active_tab_and_connected_client_id!(
|
||||
screen,
|
||||
client_id,
|
||||
|tab: &mut Tab, _client_id: ClientId| tab.hold_pane(
|
||||
pid,
|
||||
None,
|
||||
is_first_run,
|
||||
hold_for_command
|
||||
)
|
||||
);
|
||||
}
|
||||
screen.unblock_input()?;
|
||||
screen.update_tabs()?;
|
||||
screen.render()?;
|
||||
},
|
||||
ScreenInstruction::VerticalSplit(pid, initial_pane_title, client_id) => {
|
||||
ScreenInstruction::VerticalSplit(
|
||||
pid,
|
||||
initial_pane_title,
|
||||
hold_for_command,
|
||||
client_id,
|
||||
) => {
|
||||
active_tab_and_connected_client_id!(
|
||||
screen,
|
||||
client_id,
|
||||
|tab: &mut Tab, client_id: ClientId| tab.vertical_split(pid, initial_pane_title, client_id),
|
||||
?
|
||||
);
|
||||
if let Some(hold_for_command) = hold_for_command {
|
||||
let is_first_run = true;
|
||||
active_tab_and_connected_client_id!(
|
||||
screen,
|
||||
client_id,
|
||||
|tab: &mut Tab, _client_id: ClientId| tab.hold_pane(
|
||||
pid,
|
||||
None,
|
||||
is_first_run,
|
||||
hold_for_command
|
||||
)
|
||||
);
|
||||
}
|
||||
screen.unblock_input()?;
|
||||
screen.update_tabs()?;
|
||||
screen.render()?;
|
||||
@ -1644,18 +1706,20 @@ pub(crate) fn screen_thread_main(
|
||||
screen.unblock_input()?;
|
||||
},
|
||||
ScreenInstruction::HoldPane(id, exit_status, run_command, client_id) => {
|
||||
let is_first_run = false;
|
||||
match client_id {
|
||||
Some(client_id) => {
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| tab.hold_pane(
|
||||
id,
|
||||
exit_status,
|
||||
is_first_run,
|
||||
run_command
|
||||
));
|
||||
},
|
||||
None => {
|
||||
for tab in screen.tabs.values_mut() {
|
||||
if tab.get_all_pane_ids().contains(&id) {
|
||||
tab.hold_pane(id, exit_status, run_command);
|
||||
tab.hold_pane(id, exit_status, is_first_run, run_command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,8 @@ macro_rules! resize_pty {
|
||||
};
|
||||
}
|
||||
|
||||
type HoldForCommand = Option<RunCommand>;
|
||||
|
||||
// FIXME: This should be replaced by `RESIZE_PERCENT` at some point
|
||||
pub const MIN_TERMINAL_HEIGHT: usize = 5;
|
||||
pub const MIN_TERMINAL_WIDTH: usize = 5;
|
||||
@ -354,7 +356,7 @@ pub trait Pane {
|
||||
// False by default (only terminal-panes support alternate mode)
|
||||
false
|
||||
}
|
||||
fn hold(&mut self, _exit_status: Option<i32>, _run_command: RunCommand) {
|
||||
fn hold(&mut self, _exit_status: Option<i32>, _is_first_run: bool, _run_command: RunCommand) {
|
||||
// No-op by default, only terminal panes support holding
|
||||
}
|
||||
}
|
||||
@ -470,7 +472,7 @@ impl Tab {
|
||||
pub fn apply_layout(
|
||||
&mut self,
|
||||
layout: PaneLayout,
|
||||
new_ids: Vec<u32>,
|
||||
new_ids: Vec<(u32, HoldForCommand)>,
|
||||
tab_index: usize,
|
||||
client_id: ClientId,
|
||||
) -> Result<()> {
|
||||
@ -532,7 +534,7 @@ impl Tab {
|
||||
set_focus_pane_id(layout, PaneId::Plugin(pid));
|
||||
} else {
|
||||
// there are still panes left to fill, use the pids we received in this method
|
||||
if let Some(pid) = new_ids.next() {
|
||||
if let Some((pid, hold_for_command)) = new_ids.next() {
|
||||
let next_terminal_position = self.get_next_terminal_position();
|
||||
let initial_title = match &layout.run {
|
||||
Some(Run::Command(run_command)) => Some(run_command.to_string()),
|
||||
@ -552,6 +554,9 @@ impl Tab {
|
||||
initial_title,
|
||||
);
|
||||
new_pane.set_borderless(layout.borderless);
|
||||
if let Some(held_command) = hold_for_command {
|
||||
new_pane.hold(None, true, held_command.clone());
|
||||
}
|
||||
self.tiled_panes.add_pane_with_existing_geom(
|
||||
PaneId::Terminal(*pid),
|
||||
Box::new(new_pane),
|
||||
@ -560,7 +565,7 @@ impl Tab {
|
||||
}
|
||||
}
|
||||
}
|
||||
for unused_pid in new_ids {
|
||||
for (unused_pid, _) in new_ids {
|
||||
// this is a bit of a hack and happens because we don't have any central location that
|
||||
// can query the screen as to how many panes it needs to create a layout
|
||||
// fixing this will require a bit of an architecture change
|
||||
@ -601,7 +606,7 @@ impl Tab {
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
for unused_pid in new_ids {
|
||||
for (unused_pid, _) in new_ids {
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(unused_pid)))
|
||||
.with_context(err_context)?;
|
||||
@ -1746,11 +1751,19 @@ impl Tab {
|
||||
closed_pane
|
||||
}
|
||||
}
|
||||
pub fn hold_pane(&mut self, id: PaneId, exit_status: Option<i32>, run_command: RunCommand) {
|
||||
pub fn hold_pane(
|
||||
&mut self,
|
||||
id: PaneId,
|
||||
exit_status: Option<i32>,
|
||||
is_first_run: bool,
|
||||
run_command: RunCommand,
|
||||
) {
|
||||
if self.floating_panes.panes_contain(&id) {
|
||||
self.floating_panes.hold_pane(id, exit_status, run_command);
|
||||
self.floating_panes
|
||||
.hold_pane(id, exit_status, is_first_run, run_command);
|
||||
} else {
|
||||
self.tiled_panes.hold_pane(id, exit_status, run_command);
|
||||
self.tiled_panes
|
||||
.hold_pane(id, exit_status, is_first_run, run_command);
|
||||
}
|
||||
}
|
||||
pub fn replace_pane_with_suppressed_pane(&mut self, pane_id: PaneId) -> Option<Box<dyn Pane>> {
|
||||
|
@ -223,7 +223,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
);
|
||||
tab.apply_layout(PaneLayout::default(), vec![1], index, client_id)
|
||||
tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id)
|
||||
.unwrap();
|
||||
tab
|
||||
}
|
||||
@ -277,7 +277,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
|
||||
.extract_run_instructions()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| i as u32)
|
||||
.map(|(i, _)| (i as u32, None))
|
||||
.collect();
|
||||
tab.apply_layout(tab_layout, pane_ids, index, client_id)
|
||||
.unwrap();
|
||||
@ -332,14 +332,8 @@ fn create_new_tab_with_mock_pty_writer(
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
);
|
||||
tab.apply_layout(
|
||||
// LayoutTemplate::default().try_into().unwrap(),
|
||||
PaneLayout::default(),
|
||||
vec![1],
|
||||
index,
|
||||
client_id,
|
||||
)
|
||||
.unwrap();
|
||||
tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id)
|
||||
.unwrap();
|
||||
tab
|
||||
}
|
||||
|
||||
@ -393,7 +387,7 @@ fn create_new_tab_with_sixel_support(
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
);
|
||||
tab.apply_layout(PaneLayout::default(), vec![1], index, client_id)
|
||||
tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id)
|
||||
.unwrap();
|
||||
tab
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ fn create_new_tab(size: Size) -> Tab {
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
);
|
||||
tab.apply_layout(PaneLayout::default(), vec![1], index, client_id)
|
||||
tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id)
|
||||
.unwrap();
|
||||
tab
|
||||
}
|
||||
@ -189,7 +189,7 @@ fn create_new_tab_with_cell_size(
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
);
|
||||
tab.apply_layout(PaneLayout::default(), vec![1], index, client_id)
|
||||
tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id)
|
||||
.unwrap();
|
||||
tab
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ pub struct PaneFrame {
|
||||
pub other_cursors_exist_in_session: bool,
|
||||
pub other_focused_clients: Vec<ClientId>,
|
||||
exit_status: Option<ExitStatus>,
|
||||
is_first_run: bool,
|
||||
}
|
||||
|
||||
impl PaneFrame {
|
||||
@ -109,6 +110,7 @@ impl PaneFrame {
|
||||
other_focused_clients: frame_params.other_focused_clients,
|
||||
other_cursors_exist_in_session: frame_params.other_cursors_exist_in_session,
|
||||
exit_status: None,
|
||||
is_first_run: false,
|
||||
}
|
||||
}
|
||||
pub fn add_exit_status(&mut self, exit_status: Option<i32>) {
|
||||
@ -117,6 +119,9 @@ impl PaneFrame {
|
||||
None => Some(ExitStatus::Exited),
|
||||
};
|
||||
}
|
||||
pub fn indicate_first_run(&mut self) {
|
||||
self.is_first_run = true;
|
||||
}
|
||||
fn client_cursor(&self, client_id: ClientId) -> Vec<TerminalCharacter> {
|
||||
let color = client_id_to_colors(client_id, self.style.colors);
|
||||
background_color(" ", color.map(|c| c.0))
|
||||
@ -611,11 +616,7 @@ impl PaneFrame {
|
||||
}
|
||||
fn render_held_undertitle(&self) -> Result<Vec<TerminalCharacter>> {
|
||||
let max_undertitle_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||
let exit_status = self
|
||||
.exit_status
|
||||
.with_context(|| format!("failed to render command pane status '{}'", self.title))?; // unwrap is safe because we only call this if
|
||||
|
||||
let (mut first_part, first_part_len) = self.first_held_title_part_full(exit_status);
|
||||
let (mut first_part, first_part_len) = self.first_exited_held_title_part_full();
|
||||
let mut left_boundary =
|
||||
foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color);
|
||||
let mut right_boundary =
|
||||
@ -683,7 +684,7 @@ impl PaneFrame {
|
||||
character_chunks.push(CharacterChunk::new(title, x, y));
|
||||
} else if row == self.geom.rows - 1 {
|
||||
// bottom row
|
||||
if self.exit_status.is_some() {
|
||||
if self.exit_status.is_some() || self.is_first_run {
|
||||
let x = self.geom.x;
|
||||
let y = self.geom.y + row;
|
||||
character_chunks.push(CharacterChunk::new(
|
||||
@ -727,13 +728,10 @@ impl PaneFrame {
|
||||
}
|
||||
Ok((character_chunks, None))
|
||||
}
|
||||
fn first_held_title_part_full(
|
||||
&self,
|
||||
exit_status: ExitStatus,
|
||||
) -> (Vec<TerminalCharacter>, usize) {
|
||||
fn first_exited_held_title_part_full(&self) -> (Vec<TerminalCharacter>, usize) {
|
||||
// (title part, length)
|
||||
match exit_status {
|
||||
ExitStatus::Code(exit_code) => {
|
||||
match self.exit_status {
|
||||
Some(ExitStatus::Code(exit_code)) => {
|
||||
let mut first_part = vec![];
|
||||
let left_bracket = " [ ";
|
||||
let exited_text = "EXIT CODE: ";
|
||||
@ -759,7 +757,7 @@ impl PaneFrame {
|
||||
+ right_bracket.len(),
|
||||
)
|
||||
},
|
||||
ExitStatus::Exited => {
|
||||
Some(ExitStatus::Exited) => {
|
||||
let mut first_part = vec![];
|
||||
let left_bracket = " [ ";
|
||||
let exited_text = "EXITED";
|
||||
@ -775,15 +773,20 @@ impl PaneFrame {
|
||||
left_bracket.len() + exited_text.len() + right_bracket.len(),
|
||||
)
|
||||
},
|
||||
None => (foreground_color(boundary_type::HORIZONTAL, self.color), 1),
|
||||
}
|
||||
}
|
||||
fn second_held_title_part_full(&self) -> (Vec<TerminalCharacter>, usize) {
|
||||
// (title part, length)
|
||||
let mut second_part = vec![];
|
||||
let left_enter_bracket = "<";
|
||||
let left_enter_bracket = if self.is_first_run { " <" } else { "<" };
|
||||
let enter_text = "ENTER";
|
||||
let right_enter_bracket = ">";
|
||||
let enter_tip = " to re-run, ";
|
||||
let enter_tip = if self.is_first_run {
|
||||
" to run, "
|
||||
} else {
|
||||
" to re-run, "
|
||||
};
|
||||
let left_break_bracket = "<";
|
||||
let break_text = "Ctrl-c";
|
||||
let right_break_bracket = ">";
|
||||
|
@ -284,7 +284,7 @@ impl MockScreen {
|
||||
let pane_count = pane_layout.extract_run_instructions().len();
|
||||
let mut pane_ids = vec![];
|
||||
for i in 0..pane_count {
|
||||
pane_ids.push(i as u32);
|
||||
pane_ids.push((i as u32, None));
|
||||
}
|
||||
let _ = self.to_screen.send(ScreenInstruction::NewTab(
|
||||
pane_layout,
|
||||
@ -297,7 +297,7 @@ impl MockScreen {
|
||||
let pane_count = tab_layout.extract_run_instructions().len();
|
||||
let mut pane_ids = vec![];
|
||||
for i in 0..pane_count {
|
||||
pane_ids.push(i as u32);
|
||||
pane_ids.push((i as u32, None));
|
||||
}
|
||||
let _ = self.to_screen.send(ScreenInstruction::NewTab(
|
||||
tab_layout,
|
||||
@ -427,7 +427,7 @@ macro_rules! log_actions_in_thread {
|
||||
fn new_tab(screen: &mut Screen, pid: u32) {
|
||||
let client_id = 1;
|
||||
screen
|
||||
.new_tab(PaneLayout::default(), vec![pid], client_id)
|
||||
.new_tab(PaneLayout::default(), vec![(pid, None)], client_id)
|
||||
.expect("TEST");
|
||||
}
|
||||
|
||||
@ -1822,6 +1822,7 @@ pub fn send_cli_new_pane_action_with_default_parameters() {
|
||||
floating: false,
|
||||
name: None,
|
||||
close_on_exit: false,
|
||||
start_suspended: false,
|
||||
};
|
||||
send_cli_action_to_server(
|
||||
&session_metadata,
|
||||
@ -1861,6 +1862,7 @@ pub fn send_cli_new_pane_action_with_split_direction() {
|
||||
floating: false,
|
||||
name: None,
|
||||
close_on_exit: false,
|
||||
start_suspended: false,
|
||||
};
|
||||
send_cli_action_to_server(
|
||||
&session_metadata,
|
||||
@ -1900,6 +1902,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() {
|
||||
floating: false,
|
||||
name: None,
|
||||
close_on_exit: false,
|
||||
start_suspended: false,
|
||||
};
|
||||
send_cli_action_to_server(
|
||||
&session_metadata,
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-server/src/./unit/screen_tests.rs
|
||||
assertion_line: 1981
|
||||
assertion_line: 1989
|
||||
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
|
||||
---
|
||||
[SpawnTerminal(Some(OpenFile("/file/to/edit", Some(100))), Some(false), Some("Editing: /file/to/edit"), ClientId(10)), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit]
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-server/src/./unit/screen_tests.rs
|
||||
assertion_line: 1907
|
||||
assertion_line: 1915
|
||||
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
|
||||
---
|
||||
[SpawnTerminalVertically(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true })), None, 10), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit]
|
||||
[SpawnTerminalVertically(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false })), None, 10), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit]
|
||||
|
@ -141,6 +141,10 @@ pub enum Sessions {
|
||||
/// Close the pane immediately when its command exits
|
||||
#[clap(short, long, value_parser, default_value("false"), takes_value(false))]
|
||||
close_on_exit: bool,
|
||||
|
||||
/// Start the command suspended, only running after you first presses ENTER
|
||||
#[clap(short, long, value_parser, default_value("false"), takes_value(false))]
|
||||
start_suspended: bool,
|
||||
},
|
||||
/// Edit file with default $EDITOR / $VISUAL
|
||||
#[clap(visible_alias = "e")]
|
||||
@ -252,6 +256,16 @@ pub enum CliAction {
|
||||
requires("command")
|
||||
)]
|
||||
close_on_exit: bool,
|
||||
/// Start the command suspended, only running it after the you first press ENTER
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
value_parser,
|
||||
default_value("false"),
|
||||
takes_value(false),
|
||||
requires("command")
|
||||
)]
|
||||
start_suspended: bool,
|
||||
},
|
||||
/// Open the specified file in a new zellij pane with your default EDITOR
|
||||
Edit {
|
||||
|
@ -260,11 +260,13 @@ impl Action {
|
||||
floating,
|
||||
name,
|
||||
close_on_exit,
|
||||
start_suspended,
|
||||
} => {
|
||||
if !command.is_empty() {
|
||||
let mut command = command.clone();
|
||||
let (command, args) = (PathBuf::from(command.remove(0)), command);
|
||||
let cwd = cwd.or_else(|| std::env::current_dir().ok());
|
||||
let hold_on_start = start_suspended;
|
||||
let hold_on_close = !close_on_exit;
|
||||
let run_command_action = RunCommandAction {
|
||||
command,
|
||||
@ -272,6 +274,7 @@ impl Action {
|
||||
cwd,
|
||||
direction,
|
||||
hold_on_close,
|
||||
hold_on_start,
|
||||
};
|
||||
if floating {
|
||||
Ok(vec![Action::NewFloatingPane(
|
||||
|
@ -19,6 +19,8 @@ pub struct RunCommand {
|
||||
pub cwd: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub hold_on_close: bool,
|
||||
#[serde(default)]
|
||||
pub hold_on_start: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RunCommand {
|
||||
@ -50,6 +52,8 @@ pub struct RunCommandAction {
|
||||
pub direction: Option<Direction>,
|
||||
#[serde(default)]
|
||||
pub hold_on_close: bool,
|
||||
#[serde(default)]
|
||||
pub hold_on_start: bool,
|
||||
}
|
||||
|
||||
impl From<RunCommandAction> for RunCommand {
|
||||
@ -59,6 +63,7 @@ impl From<RunCommandAction> for RunCommand {
|
||||
args: action.args,
|
||||
cwd: action.cwd,
|
||||
hold_on_close: action.hold_on_close,
|
||||
hold_on_start: action.hold_on_start,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ impl Run {
|
||||
}
|
||||
}
|
||||
pub fn add_close_on_exit(&mut self, close_on_exit: Option<bool>) {
|
||||
// overrides the args of a Run::Command if they are Some
|
||||
// overrides the hold_on_close of a Run::Command if it is Some
|
||||
// and not empty
|
||||
if let Some(close_on_exit) = close_on_exit {
|
||||
if let Run::Command(run_command) = self {
|
||||
@ -150,6 +150,15 @@ impl Run {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn add_start_suspended(&mut self, start_suspended: Option<bool>) {
|
||||
// overrides the hold_on_start of a Run::Command if they are Some
|
||||
// and not empty
|
||||
if let Some(start_suspended) = start_suspended {
|
||||
if let Run::Command(run_command) = self {
|
||||
run_command.hold_on_start = start_suspended;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
|
@ -281,6 +281,19 @@ fn layout_with_command_panes_and_close_on_exit() {
|
||||
assert_snapshot!(format!("{:#?}", layout));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layout_with_command_panes_and_start_suspended() {
|
||||
let kdl_layout = r#"
|
||||
layout {
|
||||
pane command="htop" {
|
||||
start_suspended true
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None).unwrap();
|
||||
assert_snapshot!(format!("{:#?}", layout));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layout_with_plugin_panes() {
|
||||
let kdl_layout = r#"
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1050
|
||||
assertion_line: 1081
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -23,6 +23,7 @@ Layout {
|
||||
args: [],
|
||||
cwd: None,
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -45,6 +46,7 @@ Layout {
|
||||
],
|
||||
cwd: None,
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1033
|
||||
assertion_line: 1046
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -26,6 +26,7 @@ Layout {
|
||||
],
|
||||
cwd: None,
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -48,6 +49,7 @@ Layout {
|
||||
],
|
||||
cwd: None,
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -23,6 +23,7 @@ Layout {
|
||||
args: [],
|
||||
cwd: None,
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -42,6 +43,7 @@ Layout {
|
||||
args: [],
|
||||
cwd: None,
|
||||
hold_on_close: false,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -23,6 +23,7 @@ Layout {
|
||||
args: [],
|
||||
cwd: None,
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -42,6 +43,7 @@ Layout {
|
||||
args: [],
|
||||
cwd: None,
|
||||
hold_on_close: false,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1085
|
||||
assertion_line: 1133
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -23,6 +23,7 @@ Layout {
|
||||
args: [],
|
||||
cwd: None,
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -44,6 +45,7 @@ Layout {
|
||||
"/home",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1068
|
||||
assertion_line: 1116
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -25,6 +25,7 @@ Layout {
|
||||
"/tmp",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -46,6 +47,7 @@ Layout {
|
||||
"/",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1441
|
||||
assertion_line: 1516
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -39,6 +39,7 @@ Layout {
|
||||
"/tmp/./foo/./bar",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1462
|
||||
assertion_line: 1537
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -43,6 +43,7 @@ Layout {
|
||||
"/tmp/./foo/./bar",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1481
|
||||
assertion_line: 1556
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -39,6 +39,7 @@ Layout {
|
||||
"/tmp/./foo/./bar",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1283
|
||||
assertion_line: 1403
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -39,6 +39,7 @@ Layout {
|
||||
"/tmp",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1309
|
||||
assertion_line: 1435
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -39,6 +39,7 @@ Layout {
|
||||
"/tmp",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1324
|
||||
assertion_line: 1455
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -39,6 +39,7 @@ Layout {
|
||||
"/tmp",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1295
|
||||
assertion_line: 1416
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -39,6 +39,7 @@ Layout {
|
||||
"/home/foo",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1396
|
||||
assertion_line: 1471
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -39,6 +39,7 @@ Layout {
|
||||
"/tmp/./foo",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -23,6 +23,7 @@ Layout {
|
||||
args: [],
|
||||
cwd: None,
|
||||
hold_on_close: false,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -0,0 +1,42 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 294
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
tabs: [],
|
||||
focused_tab_index: None,
|
||||
template: Some(
|
||||
PaneLayout {
|
||||
children_split_direction: Horizontal,
|
||||
name: None,
|
||||
children: [
|
||||
PaneLayout {
|
||||
children_split_direction: Horizontal,
|
||||
name: None,
|
||||
children: [],
|
||||
split_size: None,
|
||||
run: Some(
|
||||
Command(
|
||||
RunCommand {
|
||||
command: "htop",
|
||||
args: [],
|
||||
cwd: None,
|
||||
hold_on_close: true,
|
||||
hold_on_start: true,
|
||||
},
|
||||
),
|
||||
),
|
||||
borderless: false,
|
||||
focus: None,
|
||||
external_children_index: None,
|
||||
},
|
||||
],
|
||||
split_size: None,
|
||||
run: None,
|
||||
borderless: false,
|
||||
focus: None,
|
||||
external_children_index: None,
|
||||
},
|
||||
),
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 570
|
||||
assertion_line: 583
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -41,6 +41,7 @@ Layout {
|
||||
args: [],
|
||||
cwd: None,
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1199
|
||||
assertion_line: 1287
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -25,6 +25,7 @@ Layout {
|
||||
"/tmp/bar",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1144
|
||||
assertion_line: 1233
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -25,6 +25,7 @@ Layout {
|
||||
"/tmp/foo",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1161
|
||||
assertion_line: 1250
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -25,6 +25,7 @@ Layout {
|
||||
"/tmp/bar",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1180
|
||||
assertion_line: 1268
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -25,6 +25,7 @@ Layout {
|
||||
"/tmp/bar",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1216
|
||||
assertion_line: 1305
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -25,6 +25,7 @@ Layout {
|
||||
"/tmp/bar",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1269
|
||||
assertion_line: 1357
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -25,6 +25,7 @@ Layout {
|
||||
"/tmp/bar",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1251
|
||||
assertion_line: 1339
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -25,6 +25,7 @@ Layout {
|
||||
"/tmp/foo",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1411
|
||||
assertion_line: 1486
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -39,6 +39,7 @@ Layout {
|
||||
"/tmp",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 1426
|
||||
assertion_line: 1501
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
@ -39,6 +39,7 @@ Layout {
|
||||
"/tmp/./foo",
|
||||
),
|
||||
hold_on_close: true,
|
||||
hold_on_start: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -54,6 +54,7 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
|| word == "tab"
|
||||
|| word == "args"
|
||||
|| word == "close_on_exit"
|
||||
|| word == "start_suspended"
|
||||
|| word == "borderless"
|
||||
|| word == "focus"
|
||||
|| word == "name"
|
||||
@ -72,6 +73,7 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
|| property_name == "cwd"
|
||||
|| property_name == "args"
|
||||
|| property_name == "close_on_exit"
|
||||
|| property_name == "start_suspended"
|
||||
|| property_name == "split_direction"
|
||||
|| property_name == "pane"
|
||||
|| property_name == "children"
|
||||
@ -241,15 +243,19 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
let args = self.parse_args(pane_node)?;
|
||||
let close_on_exit =
|
||||
kdl_get_bool_property_or_child_value_with_error!(pane_node, "close_on_exit");
|
||||
let start_suspended =
|
||||
kdl_get_bool_property_or_child_value_with_error!(pane_node, "start_suspended");
|
||||
if !is_template {
|
||||
self.assert_no_bare_attributes_in_pane_node(
|
||||
&command,
|
||||
&args,
|
||||
&close_on_exit,
|
||||
&start_suspended,
|
||||
pane_node,
|
||||
)?;
|
||||
}
|
||||
let hold_on_close = close_on_exit.map(|c| !c).unwrap_or(true);
|
||||
let hold_on_start = start_suspended.map(|c| c).unwrap_or(false);
|
||||
match (command, edit, cwd) {
|
||||
(None, None, Some(cwd)) => Ok(Some(Run::Cwd(cwd))),
|
||||
(Some(command), None, cwd) => Ok(Some(Run::Command(RunCommand {
|
||||
@ -257,6 +263,7 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
args: args.unwrap_or_else(|| vec![]),
|
||||
cwd,
|
||||
hold_on_close,
|
||||
hold_on_start,
|
||||
}))),
|
||||
(None, Some(edit), Some(cwd)) => Ok(Some(Run::EditFile(cwd.join(edit), None))),
|
||||
(None, Some(edit), None) => Ok(Some(Run::EditFile(edit, None))),
|
||||
@ -380,6 +387,8 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
let args = self.parse_args(kdl_node)?;
|
||||
let close_on_exit =
|
||||
kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit");
|
||||
let start_suspended =
|
||||
kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended");
|
||||
let split_size = self.parse_split_size(kdl_node)?;
|
||||
let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?;
|
||||
self.assert_no_bare_attributes_in_pane_node_with_template(
|
||||
@ -387,6 +396,7 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
&pane_template.run,
|
||||
&args,
|
||||
&close_on_exit,
|
||||
&start_suspended,
|
||||
kdl_node,
|
||||
)?;
|
||||
self.insert_children_to_pane_template(
|
||||
@ -396,10 +406,11 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
)?;
|
||||
pane_template.run = Run::merge(&pane_template.run, &run);
|
||||
if let Some(pane_template_run_command) = pane_template.run.as_mut() {
|
||||
// we need to do this because panes consuming a pane_templates
|
||||
// we need to do this because panes consuming a pane_template
|
||||
// can have bare args without a command
|
||||
pane_template_run_command.add_args(args);
|
||||
pane_template_run_command.add_close_on_exit(close_on_exit);
|
||||
pane_template_run_command.add_start_suspended(start_suspended);
|
||||
};
|
||||
if let Some(borderless) = borderless {
|
||||
pane_template.borderless = borderless;
|
||||
@ -600,6 +611,7 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
pane_template_run: &Option<Run>,
|
||||
args: &Option<Vec<String>>,
|
||||
close_on_exit: &Option<bool>,
|
||||
start_suspended: &Option<bool>,
|
||||
pane_node: &KdlNode,
|
||||
) -> Result<(), ConfigError> {
|
||||
if let (None, None, true) = (pane_run, pane_template_run, args.is_some()) {
|
||||
@ -614,6 +626,12 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
pane_node
|
||||
));
|
||||
}
|
||||
if let (None, None, true) = (pane_run, pane_template_run, start_suspended.is_some()) {
|
||||
return Err(kdl_parsing_error!(
|
||||
format!("start_suspended can only be specified if a command was specified either in the pane_template or in the pane"),
|
||||
pane_node
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn assert_no_bare_attributes_in_pane_node(
|
||||
@ -621,6 +639,7 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
command: &Option<PathBuf>,
|
||||
args: &Option<Vec<String>>,
|
||||
close_on_exit: &Option<bool>,
|
||||
start_suspended: &Option<bool>,
|
||||
pane_node: &KdlNode,
|
||||
) -> Result<(), ConfigError> {
|
||||
if command.is_none() {
|
||||
@ -631,6 +650,13 @@ impl<'a> KdlLayoutParser<'a> {
|
||||
pane_node.span().len(),
|
||||
));
|
||||
}
|
||||
if start_suspended.is_some() {
|
||||
return Err(ConfigError::new_layout_kdl_error(
|
||||
"start_suspended can only be set if a command was specified".into(),
|
||||
pane_node.span().offset(),
|
||||
pane_node.span().len(),
|
||||
));
|
||||
}
|
||||
if args.is_some() {
|
||||
return Err(ConfigError::new_layout_kdl_error(
|
||||
"args can only be set if a command was specified".into(),
|
||||
|
@ -1,7 +1,6 @@
|
||||
mod kdl_layout_parser;
|
||||
use crate::data::{InputMode, Key, Palette, PaletteColor};
|
||||
use crate::envs::EnvironmentVariables;
|
||||
use crate::input::command::RunCommand;
|
||||
use crate::input::config::{Config, ConfigError, KdlError};
|
||||
use crate::input::keybinds::Keybinds;
|
||||
use crate::input::layout::{Layout, RunPlugin, RunPluginLocation};
|
||||
@ -339,6 +338,16 @@ pub fn kdl_child_string_value_for_entry<'a>(
|
||||
.and_then(|cwd_value| cwd_value.value().as_string())
|
||||
}
|
||||
|
||||
pub fn kdl_child_bool_value_for_entry<'a>(
|
||||
command_metadata: &'a KdlDocument,
|
||||
entry_name: &'a str,
|
||||
) -> Option<bool> {
|
||||
command_metadata
|
||||
.get(entry_name)
|
||||
.and_then(|cwd| cwd.entries().iter().next())
|
||||
.and_then(|cwd_value| cwd_value.value().as_bool())
|
||||
}
|
||||
|
||||
impl Action {
|
||||
pub fn new_from_bytes(
|
||||
action_name: &str,
|
||||
@ -741,12 +750,20 @@ impl TryFrom<&KdlNode> for Action {
|
||||
let direction = command_metadata
|
||||
.and_then(|c_m| kdl_child_string_value_for_entry(c_m, "direction"))
|
||||
.and_then(|direction_string| Direction::from_str(direction_string).ok());
|
||||
let hold_on_close = command_metadata
|
||||
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "close_on_exit"))
|
||||
.and_then(|close_on_exit| Some(!close_on_exit))
|
||||
.unwrap_or(true);
|
||||
let hold_on_start = command_metadata
|
||||
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "start_suspended"))
|
||||
.unwrap_or(false);
|
||||
let run_command_action = RunCommandAction {
|
||||
command: PathBuf::from(command),
|
||||
args,
|
||||
cwd,
|
||||
direction,
|
||||
hold_on_close: true,
|
||||
hold_on_close,
|
||||
hold_on_start,
|
||||
};
|
||||
Ok(Action::Run(run_command_action))
|
||||
},
|
||||
@ -1464,32 +1481,6 @@ impl Keybinds {
|
||||
}
|
||||
}
|
||||
|
||||
impl RunCommand {
|
||||
pub fn from_kdl(kdl_node: &KdlNode) -> Result<Self, ConfigError> {
|
||||
let command = PathBuf::from(kdl_get_child_entry_string_value!(kdl_node, "cmd").ok_or(
|
||||
ConfigError::new_kdl_error(
|
||||
"Command must have a cmd value".into(),
|
||||
kdl_node.span().offset(),
|
||||
kdl_node.span().len(),
|
||||
),
|
||||
)?);
|
||||
let cwd = kdl_get_child_entry_string_value!(kdl_node, "cwd").map(|c| PathBuf::from(c));
|
||||
let args = match kdl_get_child!(kdl_node, "args") {
|
||||
Some(kdl_args) => kdl_string_arguments!(kdl_args)
|
||||
.iter()
|
||||
.map(|s| String::from(*s))
|
||||
.collect(),
|
||||
None => vec![],
|
||||
};
|
||||
Ok(RunCommand {
|
||||
command,
|
||||
args,
|
||||
cwd,
|
||||
hold_on_close: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_kdl(kdl_config: &str, base_config: Option<Config>) -> Result<Config, ConfigError> {
|
||||
let mut config = base_config.unwrap_or_else(|| Config::default());
|
||||
|
Loading…
Reference in New Issue
Block a user