Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(command-panes): allow to start suspended #1887

Merged
merged 3 commits into from
Nov 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fn main() {
floating,
name,
close_on_exit,
start_suspended,
})) = opts.command
{
let command_cli_action = CliAction::NewPane {
Expand All @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions src/tests/e2e/cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion src/tests/e2e/remote_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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 │
" ││█ │
$ ││ │
│ ││ │
│ ││ │
│ ││ │
Expand Down
34 changes: 34 additions & 0 deletions zellij-server/src/os_input_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ fn spawn_terminal(
args,
cwd: None,
hold_on_close: false,
hold_on_start: false,
}
},
TerminalAction::RunCommand(command) => command,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
3 changes: 2 additions & 1 deletion zellij-server/src/panes/floating_panes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions zellij-server/src/panes/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
117 changes: 116 additions & 1 deletion zellij-server/src/panes/terminal_character.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
)
},
}
}
52 changes: 44 additions & 8 deletions zellij-server/src/panes/terminal_pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)]
Expand All @@ -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 {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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>> {
Expand All @@ -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)]
Expand Down
Loading