From abc700fc4d10d61c969ad94fa520d7d9336dcf14 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Tue, 1 Nov 2022 09:07:25 +0100 Subject: feat(command-panes): allow to start suspended (#1887) * feat(command-panes): allow panes to start suspended * style(fmt): remove unused code * style(fmt): rustfmt --- zellij-server/src/panes/floating_panes/mod.rs | 3 +- zellij-server/src/panes/grid.rs | 1 + zellij-server/src/panes/terminal_character.rs | 117 +++++++++++++++++++++++++- zellij-server/src/panes/terminal_pane.rs | 52 ++++++++++-- zellij-server/src/panes/tiled_panes/mod.rs | 3 +- 5 files changed, 165 insertions(+), 11 deletions(-) (limited to 'zellij-server/src/panes') diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index f686f40bb..d17be7557 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -156,11 +156,12 @@ impl FloatingPanes { &mut self, pane_id: PaneId, exit_status: Option, + 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> { self.panes.get(pane_id) diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index 67a882fb4..d9d60fefe 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -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); } diff --git a/zellij-server/src/panes/terminal_character.rs b/zellij-server/src/panes/terminal_character.rs index c6fa0adf1..68813e7a9 100644 --- a/zellij-server/src/panes/terminal_character.rs +++ b/zellij-server/src/panes/terminal_character.rs @@ -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 + ) + }, + } +} diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 631df0c84..5726ef44b 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -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 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, 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, 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, // 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, run_command: RunCommand) { - self.is_held = Some((exit_status, run_command)); + fn hold(&mut self, exit_status: Option, 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> { @@ -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)] diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 59657c153..7e7b7a1f9 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -1010,11 +1010,12 @@ impl TiledPanes { &mut self, pane_id: PaneId, exit_status: Option, + 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) -- cgit v1.2.3