feat(ux): rerun command pane (#1787)
* chore(config): default kdl keybindings config * tests * work * refactor(config): move stuff around * work * tab merge layout * work * work * layouts working * work * layout tests * work * work * feat(parsing): kdl layouts without config * refactor(kdl): move stuff around * work * tests(layout): add cases and fix bugs * work * fix(kdl): various bugs * chore(layouts): move all layouts to kdl * feat(kdl): shared keybidns * fix(layout): do not count fixed panes toward percentile * fix(keybinds): missing keybinds and actions * fix(config): adjust default tips * refactor(config): move stuff around * fix(tests): make e2e tests pass * fix(kdl): add verbose parsing errors * fix(kdl): focused tab * fix(layout): corret default_tab_template behavior * style(code): fix compile warnings * feat(cli): send actions through the cli * fix(cli): exit only when action is done * fix(cli): open embedded pane from floating pane * fix(cli): send actions to other sessions * feat(cli): command alias * feat(converter): convert old config * feat(converter): convert old layout and theme files * feat(kdl): pretty errors * feat(client): convert old YAML files on startup * fix: various bugs and styling issues * fix: e2e tests * fix(screen): propagate errors after merge * style(clippy): lower clippy level * fix(tests): own session_name variable * style(fmt): rustfmt * fix(cli): various action fixes * style(fmt): rustfmt * fix(themes): loading of theme files * style(fmt): rustfmt * fix(tests): theme fixtures * fix(layouts): better errors on unknown nodes * fix(kdl): clarify valid node terminator error * fix(e2e): adjust close tab test * fix(e2e): adjust close tab test again * style(code): cleanup some comments * get command panes not to exit on command exit * separate terminal pane_ids from raw_fds * render frame according to exit status * re-run command on enter and close pane on ctrl-c * proper error when command is not found * make ui nicer * initial pane title for command panes * fix pane override bug * reap terminal_ids from os_input_output on pane close * bool floating flag * some ui tweaks * fix tests * make rustfmt happy * e2e test for command pane * fix various concurrency issues * rename command to run in the cli * rustfmt * style(fmt): rustfmt * fix(e2e): command => run * fix(e2e): command => run in snapshot too!
This commit is contained in:
parent
cb926119bc
commit
c64bf5207a
44 changed files with 1925 additions and 810 deletions
|
|
@ -19,7 +19,7 @@ fn main() {
|
|||
commands::send_action_to_session(cli_action, opts.session);
|
||||
std::process::exit(0);
|
||||
}
|
||||
if let Some(Command::Sessions(Sessions::Command {
|
||||
if let Some(Command::Sessions(Sessions::Run {
|
||||
command,
|
||||
direction,
|
||||
cwd,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use super::remote_runner::{RemoteRunner, RemoteTerminal, Step};
|
|||
pub const QUIT: [u8; 1] = [17]; // ctrl-q
|
||||
pub const ESC: [u8; 1] = [27];
|
||||
pub const ENTER: [u8; 1] = [10]; // char '\n'
|
||||
pub const SPACE: [u8; 1] = [32];
|
||||
pub const LOCK_MODE: [u8; 1] = [7]; // ctrl-g
|
||||
|
||||
pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h
|
||||
|
|
@ -1876,3 +1877,93 @@ pub fn undo_rename_pane() {
|
|||
};
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn send_command_through_the_cli() {
|
||||
// here we test the following flow:
|
||||
// - send a command through the cli to run a bash script in a temporary folder
|
||||
// - have it open a "command pane" that can be re-run with Enter
|
||||
// - press Enter in the command pane to re-run the script
|
||||
//
|
||||
// the script appends the word "foo" to a temporary file and then `cat`s that file,
|
||||
// so when we press "Enter", it will run again and we'll see two "foo"s one after the other,
|
||||
// that's how we know the whole flow is working
|
||||
let fake_win_size = Size {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
};
|
||||
let mut test_attempts = 10;
|
||||
let last_snapshot = loop {
|
||||
RemoteRunner::kill_running_sessions(fake_win_size);
|
||||
let mut runner = RemoteRunner::new(fake_win_size)
|
||||
.add_step(Step {
|
||||
name: "Run command through the cli",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.status_bar_appears()
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
let fixture_folder = remote_terminal.path_to_fixture_folder();
|
||||
remote_terminal.send_command_through_the_cli(&format!(
|
||||
"{}/append-echo-script.sh",
|
||||
fixture_folder
|
||||
));
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for command to run",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("<Ctrl-c>")
|
||||
&& remote_terminal.cursor_position_is(61, 3)
|
||||
{
|
||||
remote_terminal.send_key(&SPACE); // re-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 script to run again",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("<Ctrl-c>")
|
||||
&& remote_terminal.cursor_position_is(61, 4)
|
||||
{
|
||||
step_is_complete = true
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
});
|
||||
runner.run_all_steps();
|
||||
|
||||
let last_snapshot = runner.take_snapshot_after(Step {
|
||||
name: "Wait for script to run twice",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("foo")
|
||||
&& remote_terminal.cursor_position_is(61, 4)
|
||||
{
|
||||
step_is_complete = true
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
});
|
||||
|
||||
if runner.test_timed_out && test_attempts > 0 {
|
||||
test_attempts -= 1;
|
||||
continue;
|
||||
} else {
|
||||
break last_snapshot;
|
||||
}
|
||||
};
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ fn stop_zellij(channel: &mut ssh2::Channel) {
|
|||
.write_all(b"find /tmp | grep status-bar-tips | xargs rm\n")
|
||||
.unwrap();
|
||||
channel.write_all(b"killall -KILL zellij\n").unwrap();
|
||||
channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous
|
||||
// tests
|
||||
}
|
||||
|
||||
fn start_zellij(channel: &mut ssh2::Channel) {
|
||||
|
|
@ -198,6 +200,7 @@ fn read_from_channel(
|
|||
sixel_image_store,
|
||||
Rc::new(RefCell::new(Palette::default())),
|
||||
Rc::new(RefCell::new(HashMap::new())),
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
loop {
|
||||
if !should_keep_running.load(Ordering::SeqCst) {
|
||||
|
|
@ -339,6 +342,16 @@ impl RemoteTerminal {
|
|||
channel.flush().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN
|
||||
}
|
||||
pub fn send_command_through_the_cli(&mut self, command: &str) {
|
||||
let mut channel = self.channel.lock().unwrap();
|
||||
channel
|
||||
.write_all(format!("{} run \"{}\"\n", ZELLIJ_EXECUTABLE_LOCATION, command).as_bytes())
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
}
|
||||
pub fn path_to_fixture_folder(&self) -> String {
|
||||
ZELLIJ_FIXTURE_PATH.to_string()
|
||||
}
|
||||
pub fn load_fixture(&mut self, name: &str) {
|
||||
let mut channel = self.channel.lock().unwrap();
|
||||
channel
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
source: src/tests/e2e/cases.rs
|
||||
assertion_line: 1968
|
||||
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 │
|
||||
│$ ││█ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
│ ││ │
|
||||
└──────────────────────────────────────────────────────────┘└ [ EXIT CODE: 0 ] <ENTER> to re-run, <Ctrl-c> to exit ────┘
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||
2
src/tests/fixtures/append-echo-script.sh
vendored
Executable file
2
src/tests/fixtures/append-echo-script.sh
vendored
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env bash
|
||||
echo foo >> /tmp/foo && cat /tmp/foo
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
use crate::panes::PaneId;
|
||||
|
|
@ -39,7 +39,7 @@ pub use nix::unistd::Pid;
|
|||
|
||||
use crate::ClientId;
|
||||
|
||||
pub(crate) fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
|
||||
fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
|
||||
// TODO: do this with the nix ioctl
|
||||
use libc::ioctl;
|
||||
use libc::TIOCSWINSZ;
|
||||
|
|
@ -61,18 +61,19 @@ pub(crate) fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
|
|||
|
||||
/// Handle some signals for the child process. This will loop until the child
|
||||
/// process exits.
|
||||
fn handle_command_exit(mut child: Child) {
|
||||
fn handle_command_exit(mut child: Child) -> Option<i32> {
|
||||
// returns the exit status, if any
|
||||
let mut should_exit = false;
|
||||
let mut attempts = 3;
|
||||
let mut signals = signal_hook::iterator::Signals::new(&[SIGINT, SIGTERM]).unwrap();
|
||||
'handle_exit: loop {
|
||||
// test whether the child process has exited
|
||||
match child.try_wait() {
|
||||
Ok(Some(_status)) => {
|
||||
Ok(Some(status)) => {
|
||||
// if the child process has exited, break outside of the loop
|
||||
// and exit this function
|
||||
// TODO: handle errors?
|
||||
break 'handle_exit;
|
||||
break 'handle_exit status.code();
|
||||
},
|
||||
Ok(None) => {
|
||||
::std::thread::sleep(::std::time::Duration::from_millis(10));
|
||||
|
|
@ -94,50 +95,80 @@ fn handle_command_exit(mut child: Child) {
|
|||
} else {
|
||||
// when I say whoa, I mean WHOA!
|
||||
let _ = child.kill();
|
||||
break 'handle_exit;
|
||||
break 'handle_exit None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn command_exists(cmd: &RunCommand) -> bool {
|
||||
let command = &cmd.command;
|
||||
match cmd.cwd.as_ref() {
|
||||
Some(cwd) => {
|
||||
if cwd.join(&command).exists() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
if command.exists() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if let Some(paths) = env::var_os("PATH") {
|
||||
for path in env::split_paths(&paths) {
|
||||
if path.join(command).exists() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn handle_openpty(
|
||||
open_pty_res: OpenptyResult,
|
||||
cmd: RunCommand,
|
||||
quit_cb: Box<dyn Fn(PaneId) + Send>,
|
||||
) -> (RawFd, RawFd) {
|
||||
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status
|
||||
terminal_id: u32,
|
||||
) -> Result<(RawFd, RawFd), SpawnTerminalError> {
|
||||
// primary side of pty and child fd
|
||||
let pid_primary = open_pty_res.master;
|
||||
let pid_secondary = open_pty_res.slave;
|
||||
|
||||
let mut child = unsafe {
|
||||
let command = &mut Command::new(cmd.command);
|
||||
if let Some(current_dir) = cmd.cwd {
|
||||
if current_dir.exists() {
|
||||
command.current_dir(current_dir);
|
||||
}
|
||||
}
|
||||
command
|
||||
.args(&cmd.args)
|
||||
.pre_exec(move || -> std::io::Result<()> {
|
||||
if libc::login_tty(pid_secondary) != 0 {
|
||||
panic!("failed to set controlling terminal");
|
||||
if command_exists(&cmd) {
|
||||
let mut child = unsafe {
|
||||
let cmd = cmd.clone();
|
||||
let command = &mut Command::new(cmd.command);
|
||||
if let Some(current_dir) = cmd.cwd {
|
||||
if current_dir.exists() {
|
||||
command.current_dir(current_dir);
|
||||
}
|
||||
close_fds::close_open_fds(3, &[]);
|
||||
Ok(())
|
||||
})
|
||||
.spawn()
|
||||
.expect("failed to spawn")
|
||||
};
|
||||
}
|
||||
command
|
||||
.args(&cmd.args)
|
||||
.pre_exec(move || -> std::io::Result<()> {
|
||||
if libc::login_tty(pid_secondary) != 0 {
|
||||
panic!("failed to set controlling terminal");
|
||||
}
|
||||
close_fds::close_open_fds(3, &[]);
|
||||
Ok(())
|
||||
})
|
||||
.spawn()
|
||||
.expect("failed to spawn")
|
||||
};
|
||||
|
||||
let child_id = child.id();
|
||||
std::thread::spawn(move || {
|
||||
child.wait().unwrap();
|
||||
handle_command_exit(child);
|
||||
let _ = nix::unistd::close(pid_primary);
|
||||
let _ = nix::unistd::close(pid_secondary);
|
||||
quit_cb(PaneId::Terminal(pid_primary));
|
||||
});
|
||||
let child_id = child.id();
|
||||
std::thread::spawn(move || {
|
||||
child.wait().unwrap();
|
||||
let exit_status = handle_command_exit(child);
|
||||
let _ = nix::unistd::close(pid_secondary);
|
||||
quit_cb(PaneId::Terminal(terminal_id), exit_status, cmd);
|
||||
});
|
||||
|
||||
(pid_primary, child_id as RawFd)
|
||||
Ok((pid_primary, child_id as RawFd))
|
||||
} else {
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id))
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios)
|
||||
|
|
@ -147,16 +178,20 @@ fn handle_terminal(
|
|||
cmd: RunCommand,
|
||||
failover_cmd: Option<RunCommand>,
|
||||
orig_termios: termios::Termios,
|
||||
quit_cb: Box<dyn Fn(PaneId) + Send>,
|
||||
) -> (RawFd, RawFd) {
|
||||
// Create a pipe to allow the child the communicate the shell's pid to it's
|
||||
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>,
|
||||
terminal_id: u32,
|
||||
) -> Result<(RawFd, RawFd), SpawnTerminalError> {
|
||||
// Create a pipe to allow the child the communicate the shell's pid to its
|
||||
// parent.
|
||||
match openpty(None, Some(&orig_termios)) {
|
||||
Ok(open_pty_res) => handle_openpty(open_pty_res, cmd, quit_cb),
|
||||
Ok(open_pty_res) => handle_openpty(open_pty_res, cmd, quit_cb, terminal_id),
|
||||
Err(e) => match failover_cmd {
|
||||
Some(failover_cmd) => handle_terminal(failover_cmd, None, orig_termios, quit_cb),
|
||||
Some(failover_cmd) => {
|
||||
handle_terminal(failover_cmd, None, orig_termios, quit_cb, terminal_id)
|
||||
},
|
||||
None => {
|
||||
panic!("failed to start pty{:?}", e);
|
||||
log::error!("Failed to start pty: {:?}", e);
|
||||
Err(SpawnTerminalError::FailedToStartPty)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -192,12 +227,15 @@ fn separate_command_arguments(command: &mut PathBuf, args: &mut Vec<String>) {
|
|||
///
|
||||
/// This function will panic if both the `EDITOR` and `VISUAL` environment variables are not
|
||||
/// set.
|
||||
pub fn spawn_terminal(
|
||||
fn spawn_terminal(
|
||||
terminal_action: TerminalAction,
|
||||
orig_termios: termios::Termios,
|
||||
quit_cb: Box<dyn Fn(PaneId) + Send>,
|
||||
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit_status
|
||||
default_editor: Option<PathBuf>,
|
||||
) -> Result<(RawFd, RawFd), &'static str> {
|
||||
terminal_id: u32,
|
||||
) -> Result<(RawFd, RawFd), SpawnTerminalError> {
|
||||
// returns the terminal_id, the primary fd and the
|
||||
// secondary fd
|
||||
let mut failover_cmd_args = None;
|
||||
let cmd = match terminal_action {
|
||||
TerminalAction::OpenFile(file_to_open, line_number) => {
|
||||
|
|
@ -205,9 +243,7 @@ pub fn spawn_terminal(
|
|||
&& env::var("EDITOR").is_err()
|
||||
&& env::var("VISUAL").is_err()
|
||||
{
|
||||
return Err(
|
||||
"No Editor found, consider setting a path to one in $EDITOR or $VISUAL",
|
||||
);
|
||||
return Err(SpawnTerminalError::NoEditorFound);
|
||||
}
|
||||
|
||||
let mut command = default_editor.unwrap_or_else(|| {
|
||||
|
|
@ -239,6 +275,7 @@ pub fn spawn_terminal(
|
|||
command,
|
||||
args,
|
||||
cwd: None,
|
||||
hold_on_close: false,
|
||||
}
|
||||
},
|
||||
TerminalAction::RunCommand(command) => command,
|
||||
|
|
@ -251,13 +288,52 @@ pub fn spawn_terminal(
|
|||
None
|
||||
};
|
||||
|
||||
Ok(handle_terminal(cmd, failover_cmd, orig_termios, quit_cb))
|
||||
handle_terminal(cmd, failover_cmd, orig_termios, quit_cb, terminal_id)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SpawnTerminalError {
|
||||
CommandNotFound(u32), // u32 is the terminal id
|
||||
NoEditorFound,
|
||||
NoMoreTerminalIds,
|
||||
FailedToStartPty,
|
||||
GenericSpawnError(&'static str),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SpawnTerminalError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
SpawnTerminalError::CommandNotFound(terminal_id) => {
|
||||
write!(f, "Command not found for terminal_id: {}", terminal_id)
|
||||
},
|
||||
SpawnTerminalError::NoEditorFound => {
|
||||
write!(
|
||||
f,
|
||||
"No Editor found, consider setting a path to one in $EDITOR or $VISUAL"
|
||||
)
|
||||
},
|
||||
SpawnTerminalError::NoMoreTerminalIds => {
|
||||
write!(f, "No more terminal ids left to allocate.")
|
||||
},
|
||||
SpawnTerminalError::FailedToStartPty => {
|
||||
write!(f, "Failed to start pty")
|
||||
},
|
||||
SpawnTerminalError::GenericSpawnError(msg) => {
|
||||
write!(f, "{}", msg)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ServerOsInputOutput {
|
||||
orig_termios: Arc<Mutex<termios::Termios>>,
|
||||
client_senders: Arc<Mutex<HashMap<ClientId, IpcSenderWithContext<ServerToClientMsg>>>>,
|
||||
terminal_id_to_raw_fd: Arc<Mutex<BTreeMap<u32, Option<RawFd>>>>, // A value of None means the
|
||||
// terminal_id exists but is
|
||||
// not connected to an fd (eg.
|
||||
// a command pane with a
|
||||
// non-existing command)
|
||||
}
|
||||
|
||||
// async fn in traits is not supported by rust, so dtolnay's excellent async_trait macro is being
|
||||
|
|
@ -291,25 +367,24 @@ impl AsyncReader for RawFdAsyncReader {
|
|||
/// The `ServerOsApi` trait represents an abstract interface to the features of an operating system that
|
||||
/// Zellij server requires.
|
||||
pub trait ServerOsApi: Send + Sync {
|
||||
/// Sets the size of the terminal associated to file descriptor `fd`.
|
||||
fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16);
|
||||
fn set_terminal_size_using_terminal_id(&self, id: u32, cols: u16, rows: u16);
|
||||
/// Spawn a new terminal, with a terminal action. The returned tuple contains the master file
|
||||
/// descriptor of the forked pseudo terminal and a [ChildId] struct containing process id's for
|
||||
/// the forked child process.
|
||||
fn spawn_terminal(
|
||||
&self,
|
||||
terminal_action: TerminalAction,
|
||||
quit_cb: Box<dyn Fn(PaneId) + Send>,
|
||||
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status
|
||||
default_editor: Option<PathBuf>,
|
||||
) -> Result<(RawFd, RawFd), &'static str>;
|
||||
) -> Result<(u32, RawFd, RawFd), SpawnTerminalError>;
|
||||
/// 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
|
||||
fn async_file_reader(&self, fd: RawFd) -> Box<dyn AsyncReader>;
|
||||
/// Write bytes to the standard input of the virtual terminal referred to by `fd`.
|
||||
fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result<usize, nix::Error>;
|
||||
fn write_to_tty_stdin(&self, terminal_id: u32, buf: &[u8]) -> Result<usize, nix::Error>;
|
||||
/// Wait until all output written to the object referred to by `fd` has been transmitted.
|
||||
fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error>;
|
||||
fn tcdrain(&self, terminal_id: u32) -> Result<(), nix::Error>;
|
||||
/// Terminate the process with process ID `pid`. (SIGTERM)
|
||||
fn kill(&self, pid: Pid) -> Result<(), nix::Error>;
|
||||
/// Terminate the process with process ID `pid`. (SIGKILL)
|
||||
|
|
@ -332,27 +407,78 @@ pub trait ServerOsApi: Send + Sync {
|
|||
fn get_cwd(&self, pid: Pid) -> Option<PathBuf>;
|
||||
/// Writes the given buffer to a string
|
||||
fn write_to_file(&mut self, buf: String, file: Option<String>);
|
||||
|
||||
fn re_run_command_in_terminal(
|
||||
&self,
|
||||
terminal_id: u32,
|
||||
run_command: RunCommand,
|
||||
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status
|
||||
) -> Result<(RawFd, RawFd), SpawnTerminalError>;
|
||||
fn clear_terminal_id(&self, terminal_id: u32);
|
||||
}
|
||||
|
||||
impl ServerOsApi for ServerOsInputOutput {
|
||||
fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16) {
|
||||
if cols > 0 && rows > 0 {
|
||||
set_terminal_size_using_fd(fd, cols, rows);
|
||||
fn set_terminal_size_using_terminal_id(&self, id: u32, cols: u16, rows: u16) {
|
||||
match self.terminal_id_to_raw_fd.lock().unwrap().get(&id) {
|
||||
Some(Some(fd)) => {
|
||||
if cols > 0 && rows > 0 {
|
||||
set_terminal_size_using_fd(*fd, cols, rows);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
log::error!("Failed to find terminal fd for id: {id}, so cannot resize terminal");
|
||||
},
|
||||
}
|
||||
}
|
||||
fn spawn_terminal(
|
||||
&self,
|
||||
terminal_action: TerminalAction,
|
||||
quit_cb: Box<dyn Fn(PaneId) + Send>,
|
||||
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status
|
||||
default_editor: Option<PathBuf>,
|
||||
) -> Result<(RawFd, RawFd), &'static str> {
|
||||
) -> Result<(u32, RawFd, RawFd), SpawnTerminalError> {
|
||||
let orig_termios = self.orig_termios.lock().unwrap();
|
||||
spawn_terminal(
|
||||
terminal_action,
|
||||
orig_termios.clone(),
|
||||
quit_cb,
|
||||
default_editor,
|
||||
)
|
||||
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);
|
||||
match spawn_terminal(
|
||||
terminal_action,
|
||||
orig_termios.clone(),
|
||||
quit_cb,
|
||||
default_editor,
|
||||
terminal_id,
|
||||
) {
|
||||
Ok((pid_primary, pid_secondary)) => {
|
||||
self.terminal_id_to_raw_fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(terminal_id, Some(pid_primary));
|
||||
Ok((terminal_id, pid_primary, pid_secondary))
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
},
|
||||
None => Err(SpawnTerminalError::NoMoreTerminalIds),
|
||||
}
|
||||
}
|
||||
fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
unistd::read(fd, buf)
|
||||
|
|
@ -360,11 +486,25 @@ impl ServerOsApi for ServerOsInputOutput {
|
|||
fn async_file_reader(&self, fd: RawFd) -> Box<dyn AsyncReader> {
|
||||
Box::new(RawFdAsyncReader::new(fd))
|
||||
}
|
||||
fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result<usize, nix::Error> {
|
||||
unistd::write(fd, buf)
|
||||
fn write_to_tty_stdin(&self, terminal_id: u32, buf: &[u8]) -> Result<usize, nix::Error> {
|
||||
match self.terminal_id_to_raw_fd.lock().unwrap().get(&terminal_id) {
|
||||
Some(Some(fd)) => unistd::write(*fd, buf),
|
||||
_ => {
|
||||
// TODO: propagate this error
|
||||
log::error!("Failed to write to terminal with {terminal_id} - could not find its file descriptor");
|
||||
Ok(0)
|
||||
},
|
||||
}
|
||||
}
|
||||
fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error> {
|
||||
termios::tcdrain(fd)
|
||||
fn tcdrain(&self, terminal_id: u32) -> Result<(), nix::Error> {
|
||||
match self.terminal_id_to_raw_fd.lock().unwrap().get(&terminal_id) {
|
||||
Some(Some(fd)) => termios::tcdrain(*fd),
|
||||
_ => {
|
||||
// TODO: propagate this error
|
||||
log::error!("Failed to tcdrain to terminal with {terminal_id} - could not find its file descriptor");
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
fn box_clone(&self) -> Box<dyn ServerOsApi> {
|
||||
Box::new((*self).clone())
|
||||
|
|
@ -430,6 +570,37 @@ impl ServerOsApi for ServerOsInputOutput {
|
|||
log::error!("could not write to file: {}", e);
|
||||
}
|
||||
}
|
||||
fn re_run_command_in_terminal(
|
||||
&self,
|
||||
terminal_id: u32,
|
||||
run_command: RunCommand,
|
||||
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status
|
||||
) -> Result<(RawFd, RawFd), SpawnTerminalError> {
|
||||
let orig_termios = self.orig_termios.lock().unwrap();
|
||||
let default_editor = None; // no need for a default editor when running an explicit command
|
||||
match spawn_terminal(
|
||||
TerminalAction::RunCommand(run_command),
|
||||
orig_termios.clone(),
|
||||
quit_cb,
|
||||
default_editor,
|
||||
terminal_id,
|
||||
) {
|
||||
Ok((pid_primary, pid_secondary)) => {
|
||||
self.terminal_id_to_raw_fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(terminal_id, Some(pid_primary));
|
||||
Ok((pid_primary, pid_secondary))
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
fn clear_terminal_id(&self, terminal_id: u32) {
|
||||
self.terminal_id_to_raw_fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(&terminal_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn ServerOsApi> {
|
||||
|
|
@ -444,6 +615,7 @@ pub fn get_server_os_input() -> Result<ServerOsInputOutput, nix::Error> {
|
|||
Ok(ServerOsInputOutput {
|
||||
orig_termios,
|
||||
client_senders: Arc::new(Mutex::new(HashMap::new())),
|
||||
terminal_id_to_raw_fd: Arc::new(Mutex::new(BTreeMap::new())),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,15 +17,16 @@ use std::rc::Rc;
|
|||
use std::time::Instant;
|
||||
use zellij_utils::{
|
||||
data::{ModeInfo, Style},
|
||||
input::command::RunCommand,
|
||||
pane_size::{Offset, PaneGeom, Size, Viewport},
|
||||
};
|
||||
|
||||
macro_rules! resize_pty {
|
||||
($pane:expr, $os_input:expr) => {
|
||||
if let PaneId::Terminal(ref pid) = $pane.pid() {
|
||||
// FIXME: This `set_terminal_size_using_fd` call would be best in
|
||||
// FIXME: This `set_terminal_size_using_terminal_id` call would be best in
|
||||
// `TerminalPane::reflow_lines`
|
||||
$os_input.set_terminal_size_using_fd(
|
||||
$os_input.set_terminal_size_using_terminal_id(
|
||||
*pid,
|
||||
$pane.get_content_columns() as u16,
|
||||
$pane.get_content_rows() as u16,
|
||||
|
|
@ -150,6 +151,16 @@ impl FloatingPanes {
|
|||
self.desired_pane_positions.remove(&pane_id);
|
||||
self.panes.remove(&pane_id)
|
||||
}
|
||||
pub fn hold_pane(
|
||||
&mut self,
|
||||
pane_id: PaneId,
|
||||
exit_status: Option<i32>,
|
||||
run_command: RunCommand,
|
||||
) {
|
||||
self.panes
|
||||
.get_mut(&pane_id)
|
||||
.map(|p| p.hold(exit_status, run_command));
|
||||
}
|
||||
pub fn get(&self, pane_id: &PaneId) -> Option<&Box<dyn Pane>> {
|
||||
self.panes.get(pane_id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1504,7 +1504,7 @@ impl Grid {
|
|||
pub fn mark_for_rerender(&mut self) {
|
||||
self.should_render = true;
|
||||
}
|
||||
fn reset_terminal_state(&mut self) {
|
||||
pub fn reset_terminal_state(&mut self) {
|
||||
self.lines_above = VecDeque::with_capacity(*SCROLL_BUFFER_SIZE.get().unwrap());
|
||||
self.lines_below = vec![];
|
||||
self.viewport = vec![Row::new(self.width).canonical()];
|
||||
|
|
|
|||
|
|
@ -113,10 +113,6 @@ impl Pane for PluginPane {
|
|||
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
||||
None
|
||||
}
|
||||
fn adjust_input_to_terminal(&self, _input_bytes: Vec<u8>) -> Vec<u8> {
|
||||
// noop
|
||||
vec![]
|
||||
}
|
||||
fn position_and_size(&self) -> PaneGeom {
|
||||
self.geom
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ use crate::panes::{
|
|||
};
|
||||
use crate::panes::{AnsiCode, LinkHandler};
|
||||
use crate::pty::VteBytes;
|
||||
use crate::tab::Pane;
|
||||
use crate::tab::{AdjustedInput, Pane};
|
||||
use crate::ClientId;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::rc::Rc;
|
||||
use std::time::{self, Instant};
|
||||
use zellij_utils::input::command::RunCommand;
|
||||
use zellij_utils::pane_size::Offset;
|
||||
use zellij_utils::{
|
||||
data::{InputMode, Palette, PaletteColor, Style},
|
||||
|
|
@ -37,6 +37,10 @@ const HOME_KEY: &[u8] = &[27, 91, 72];
|
|||
const END_KEY: &[u8] = &[27, 91, 70];
|
||||
const BRACKETED_PASTE_BEGIN: &[u8] = &[27, 91, 50, 48, 48, 126];
|
||||
const BRACKETED_PASTE_END: &[u8] = &[27, 91, 50, 48, 49, 126];
|
||||
const ENTER_NEWLINE: &[u8] = &[10];
|
||||
const ENTER_CARRIAGE_RETURN: &[u8] = &[13];
|
||||
const SPACE: &[u8] = &[32];
|
||||
const CTRL_C: &[u8] = &[3]; // TODO: check this to be sure it fits all types of CTRL_C (with mac, etc)
|
||||
const TERMINATING_STRING: &str = "\0";
|
||||
const DELETE_KEY: &str = "\u{007F}";
|
||||
const BACKSPACE_KEY: &str = "\u{0008}";
|
||||
|
|
@ -74,7 +78,7 @@ impl AnsiEncoding {
|
|||
|
||||
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
|
||||
pub enum PaneId {
|
||||
Terminal(RawFd),
|
||||
Terminal(u32),
|
||||
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +87,7 @@ pub enum PaneId {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub struct TerminalPane {
|
||||
pub grid: Grid,
|
||||
pub pid: RawFd,
|
||||
pub pid: u32,
|
||||
pub selectable: bool,
|
||||
pub geom: PaneGeom,
|
||||
pub geom_override: Option<PaneGeom>,
|
||||
|
|
@ -99,6 +103,8 @@ 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
|
||||
}
|
||||
|
||||
impl Pane for TerminalPane {
|
||||
|
|
@ -157,53 +163,83 @@ impl Pane for TerminalPane {
|
|||
.cursor_coordinates()
|
||||
.map(|(x, y)| (x + left, y + top))
|
||||
}
|
||||
fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8> {
|
||||
fn adjust_input_to_terminal(&mut self, input_bytes: Vec<u8>) -> Option<AdjustedInput> {
|
||||
// there are some cases in which the terminal state means that input sent to it
|
||||
// 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 self.grid.new_line_mode {
|
||||
if let &[13] = input_bytes.as_slice() {
|
||||
// LNM - carriage return is followed by linefeed
|
||||
return "\u{0d}\u{0a}".as_bytes().to_vec();
|
||||
};
|
||||
}
|
||||
if self.grid.cursor_key_mode {
|
||||
if let Some((_exit_status, run_command)) = &self.is_held {
|
||||
match input_bytes.as_slice() {
|
||||
LEFT_ARROW => {
|
||||
return AnsiEncoding::Left.as_vec_bytes();
|
||||
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);
|
||||
Some(AdjustedInput::ReRunCommandInThisPane(run_command))
|
||||
},
|
||||
RIGHT_ARROW => {
|
||||
return AnsiEncoding::Right.as_vec_bytes();
|
||||
},
|
||||
UP_ARROW => {
|
||||
return AnsiEncoding::Up.as_vec_bytes();
|
||||
},
|
||||
DOWN_ARROW => {
|
||||
return AnsiEncoding::Down.as_vec_bytes();
|
||||
},
|
||||
|
||||
HOME_KEY => {
|
||||
return AnsiEncoding::Home.as_vec_bytes();
|
||||
},
|
||||
END_KEY => {
|
||||
return AnsiEncoding::End.as_vec_bytes();
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
}
|
||||
|
||||
if !self.grid.bracketed_paste_mode {
|
||||
// Zellij itself operates in bracketed paste mode, so the terminal sends these
|
||||
// instructions (bracketed paste start and bracketed paste end respectively)
|
||||
// when pasting input. We only need to make sure not to send them to terminal
|
||||
// panes who do not work in this mode
|
||||
match input_bytes.as_slice() {
|
||||
BRACKETED_PASTE_BEGIN | BRACKETED_PASTE_END => return vec![],
|
||||
_ => {},
|
||||
CTRL_C => Some(AdjustedInput::CloseThisPane),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
if self.grid.new_line_mode {
|
||||
if let &[13] = input_bytes.as_slice() {
|
||||
// LNM - carriage return is followed by linefeed
|
||||
return Some(AdjustedInput::WriteBytesToTerminal(
|
||||
"\u{0d}\u{0a}".as_bytes().to_vec(),
|
||||
));
|
||||
};
|
||||
}
|
||||
if self.grid.cursor_key_mode {
|
||||
match input_bytes.as_slice() {
|
||||
LEFT_ARROW => {
|
||||
return Some(AdjustedInput::WriteBytesToTerminal(
|
||||
AnsiEncoding::Left.as_vec_bytes(),
|
||||
));
|
||||
},
|
||||
RIGHT_ARROW => {
|
||||
return Some(AdjustedInput::WriteBytesToTerminal(
|
||||
AnsiEncoding::Right.as_vec_bytes(),
|
||||
));
|
||||
},
|
||||
UP_ARROW => {
|
||||
return Some(AdjustedInput::WriteBytesToTerminal(
|
||||
AnsiEncoding::Up.as_vec_bytes(),
|
||||
));
|
||||
},
|
||||
DOWN_ARROW => {
|
||||
return Some(AdjustedInput::WriteBytesToTerminal(
|
||||
AnsiEncoding::Down.as_vec_bytes(),
|
||||
));
|
||||
},
|
||||
|
||||
HOME_KEY => {
|
||||
return Some(AdjustedInput::WriteBytesToTerminal(
|
||||
AnsiEncoding::Home.as_vec_bytes(),
|
||||
));
|
||||
},
|
||||
END_KEY => {
|
||||
return Some(AdjustedInput::WriteBytesToTerminal(
|
||||
AnsiEncoding::End.as_vec_bytes(),
|
||||
));
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
}
|
||||
|
||||
if !self.grid.bracketed_paste_mode {
|
||||
// Zellij itself operates in bracketed paste mode, so the terminal sends these
|
||||
// instructions (bracketed paste start and bracketed paste end respectively)
|
||||
// when pasting input. We only need to make sure not to send them to terminal
|
||||
// panes who do not work in this mode
|
||||
match input_bytes.as_slice() {
|
||||
BRACKETED_PASTE_BEGIN | BRACKETED_PASTE_END => {
|
||||
return Some(AdjustedInput::WriteBytesToTerminal(vec![]))
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
Some(AdjustedInput::WriteBytesToTerminal(input_bytes))
|
||||
}
|
||||
input_bytes
|
||||
}
|
||||
fn position_and_size(&self) -> PaneGeom {
|
||||
self.geom
|
||||
|
|
@ -307,7 +343,9 @@ impl Pane for TerminalPane {
|
|||
input_mode: InputMode,
|
||||
) -> Option<(Vec<CharacterChunk>, Option<String>)> {
|
||||
// TODO: remove the cursor stuff from here
|
||||
let pane_title = if self.pane_name.is_empty()
|
||||
let pane_title = if let Some((_exit_status, run_command)) = &self.is_held {
|
||||
format!("{}", run_command)
|
||||
} else if self.pane_name.is_empty()
|
||||
&& input_mode == InputMode::RenamePane
|
||||
&& frame_params.is_main_client
|
||||
{
|
||||
|
|
@ -346,12 +384,15 @@ impl Pane for TerminalPane {
|
|||
self.pane_name.clone()
|
||||
};
|
||||
|
||||
let frame = PaneFrame::new(
|
||||
let mut frame = PaneFrame::new(
|
||||
self.current_geom().into(),
|
||||
self.grid.scrollback_position_and_length(),
|
||||
pane_title,
|
||||
frame_params,
|
||||
);
|
||||
if let Some((exit_status, _run_command)) = &self.is_held {
|
||||
frame.add_exit_status(exit_status.as_ref().copied());
|
||||
}
|
||||
|
||||
match self.frame.get(&client_id) {
|
||||
// TODO: use and_then or something?
|
||||
|
|
@ -654,12 +695,16 @@ 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));
|
||||
self.set_should_render(true);
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalPane {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
pid: RawFd,
|
||||
pid: u32,
|
||||
position_and_size: PaneGeom,
|
||||
style: Style,
|
||||
pane_index: usize,
|
||||
|
|
@ -669,8 +714,10 @@ impl TerminalPane {
|
|||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||
initial_pane_title: Option<String>,
|
||||
) -> TerminalPane {
|
||||
let initial_pane_title = format!("Pane #{}", pane_index);
|
||||
let initial_pane_title =
|
||||
initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index));
|
||||
let grid = Grid::new(
|
||||
position_and_size.rows.as_usize(),
|
||||
position_and_size.cols.as_usize(),
|
||||
|
|
@ -698,6 +745,7 @@ impl TerminalPane {
|
|||
borderless: false,
|
||||
fake_cursor_locations: HashSet::new(),
|
||||
search_term: String::new(),
|
||||
is_held: None,
|
||||
}
|
||||
}
|
||||
pub fn get_x(&self) -> usize {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use std::rc::Rc;
|
|||
use std::time::Instant;
|
||||
use zellij_utils::{
|
||||
data::{ModeInfo, Style},
|
||||
input::command::RunCommand,
|
||||
input::layout::SplitDirection,
|
||||
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
|
||||
};
|
||||
|
|
@ -21,9 +22,9 @@ use zellij_utils::{
|
|||
macro_rules! resize_pty {
|
||||
($pane:expr, $os_input:expr) => {
|
||||
if let PaneId::Terminal(ref pid) = $pane.pid() {
|
||||
// FIXME: This `set_terminal_size_using_fd` call would be best in
|
||||
// FIXME: This `set_terminal_size_using_terminal_id` call would be best in
|
||||
// `TerminalPane::reflow_lines`
|
||||
$os_input.set_terminal_size_using_fd(
|
||||
$os_input.set_terminal_size_using_terminal_id(
|
||||
*pid,
|
||||
$pane.get_content_columns() as u16,
|
||||
$pane.get_content_rows() as u16,
|
||||
|
|
@ -993,6 +994,16 @@ impl TiledPanes {
|
|||
None
|
||||
}
|
||||
}
|
||||
pub fn hold_pane(
|
||||
&mut self,
|
||||
pane_id: PaneId,
|
||||
exit_status: Option<i32>,
|
||||
run_command: RunCommand,
|
||||
) {
|
||||
self.panes
|
||||
.get_mut(&pane_id)
|
||||
.map(|p| p.hold(exit_status, run_command));
|
||||
}
|
||||
pub fn panes_to_hide_contains(&self, pane_id: PaneId) -> bool {
|
||||
self.panes_to_hide.contains(&pane_id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ fn create_pane() -> TerminalPane {
|
|||
sixel_image_store,
|
||||
Rc::new(RefCell::new(Palette::default())),
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
let content = read_fixture();
|
||||
terminal_pane.handle_pty_bytes(content);
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ pub fn scrolling_inside_a_pane() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..30 {
|
||||
|
|
@ -87,6 +88,7 @@ pub fn sixel_image_inside_terminal_pane() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
let sixel_image_bytes = "\u{1b}Pq
|
||||
#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0
|
||||
|
|
@ -127,6 +129,7 @@ pub fn partial_sixel_image_inside_terminal_pane() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
let pane_content = read_fixture("sixel-image-500px.six");
|
||||
terminal_pane.handle_pty_bytes(pane_content);
|
||||
|
|
@ -161,6 +164,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
let pane_content = read_fixture("sixel-image-500px.six");
|
||||
terminal_pane.handle_pty_bytes(pane_content);
|
||||
|
|
@ -194,6 +198,7 @@ pub fn scrolling_through_a_sixel_image() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..30 {
|
||||
|
|
@ -238,6 +243,7 @@ pub fn multiple_sixel_images_in_pane() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..5 {
|
||||
|
|
@ -280,6 +286,7 @@ pub fn resizing_pane_with_sixel_images() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..5 {
|
||||
|
|
@ -325,6 +332,7 @@ pub fn changing_character_cell_size_with_sixel_images() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
let mut text_to_fill_pane = String::new();
|
||||
for i in 0..5 {
|
||||
|
|
@ -375,6 +383,7 @@ pub fn keep_working_after_corrupted_sixel_image() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
|
||||
let sixel_image_bytes = "\u{1b}PI AM CORRUPTED BWAHAHAq
|
||||
|
|
@ -423,6 +432,7 @@ pub fn pane_with_frame_position_is_on_frame() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
|
||||
terminal_pane.set_content_offset(Offset::frame(1));
|
||||
|
|
@ -507,6 +517,7 @@ pub fn pane_with_bottom_and_right_borders_position_is_on_frame() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
|
||||
terminal_pane.set_content_offset(Offset::shift(1, 1));
|
||||
|
|
@ -591,6 +602,7 @@ pub fn frameless_pane_position_is_on_frame() {
|
|||
sixel_image_store,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
None,
|
||||
); // 0 is the pane index
|
||||
|
||||
terminal_pane.set_content_offset(Offset::default());
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
use crate::os_input_output::SpawnTerminalError;
|
||||
use crate::terminal_bytes::TerminalBytes;
|
||||
use crate::{
|
||||
panes::PaneId, screen::ScreenInstruction, thread_bus::Bus, wasm_vm::PluginInstruction,
|
||||
panes::PaneId,
|
||||
screen::ScreenInstruction,
|
||||
thread_bus::{Bus, ThreadSenders},
|
||||
wasm_vm::PluginInstruction,
|
||||
ClientId, ServerInstruction,
|
||||
};
|
||||
use async_std::task::{self, JoinHandle};
|
||||
|
|
@ -27,7 +31,8 @@ pub enum ClientOrTabIndex {
|
|||
/// Instructions related to PTYs (pseudoterminals).
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum PtyInstruction {
|
||||
SpawnTerminal(Option<TerminalAction>, ClientOrTabIndex),
|
||||
SpawnTerminal(Option<TerminalAction>, Option<bool>, ClientOrTabIndex), // bool (if Some) is
|
||||
// should_float
|
||||
OpenInPlaceEditor(PathBuf, Option<usize>, ClientId), // Option<usize> is the optional line number
|
||||
SpawnTerminalVertically(Option<TerminalAction>, ClientId),
|
||||
SpawnTerminalHorizontally(Option<TerminalAction>, ClientId),
|
||||
|
|
@ -41,6 +46,7 @@ pub(crate) enum PtyInstruction {
|
|||
), // the String is the tab name
|
||||
ClosePane(PaneId),
|
||||
CloseTab(Vec<PaneId>),
|
||||
ReRunCommandInPane(PaneId, RunCommand),
|
||||
Exit,
|
||||
}
|
||||
|
||||
|
|
@ -56,6 +62,7 @@ impl From<&PtyInstruction> for PtyContext {
|
|||
PtyInstruction::ClosePane(_) => PtyContext::ClosePane,
|
||||
PtyInstruction::CloseTab(_) => PtyContext::CloseTab,
|
||||
PtyInstruction::NewTab(..) => PtyContext::NewTab,
|
||||
PtyInstruction::ReRunCommandInPane(..) => PtyContext::ReRunCommandInPane,
|
||||
PtyInstruction::Exit => PtyContext::Exit,
|
||||
}
|
||||
}
|
||||
|
|
@ -64,9 +71,9 @@ impl From<&PtyInstruction> for PtyContext {
|
|||
pub(crate) struct Pty {
|
||||
pub active_panes: HashMap<ClientId, PaneId>,
|
||||
pub bus: Bus<PtyInstruction>,
|
||||
pub id_to_child_pid: HashMap<RawFd, RawFd>, // pty_primary => child raw fd
|
||||
pub id_to_child_pid: HashMap<u32, RawFd>, // terminal_id => child raw fd
|
||||
debug_to_file: bool,
|
||||
task_handles: HashMap<RawFd, JoinHandle<()>>,
|
||||
task_handles: HashMap<u32, JoinHandle<()>>, // terminal_id to join-handle
|
||||
default_editor: Option<PathBuf>,
|
||||
}
|
||||
|
||||
|
|
@ -75,17 +82,53 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) {
|
|||
let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Pty((&event).into()));
|
||||
match event {
|
||||
PtyInstruction::SpawnTerminal(terminal_action, client_or_tab_index) => {
|
||||
let pid = pty
|
||||
.spawn_terminal(terminal_action, client_or_tab_index)
|
||||
.unwrap(); // TODO: handle error here
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::NewPane(
|
||||
PaneId::Terminal(pid),
|
||||
client_or_tab_index,
|
||||
))
|
||||
.unwrap();
|
||||
PtyInstruction::SpawnTerminal(terminal_action, should_float, client_or_tab_index) => {
|
||||
let (hold_on_close, run_command, pane_title) = match &terminal_action {
|
||||
Some(TerminalAction::RunCommand(run_command)) => (
|
||||
run_command.hold_on_close,
|
||||
Some(run_command.clone()),
|
||||
Some(run_command.to_string()),
|
||||
),
|
||||
_ => (false, None, None),
|
||||
};
|
||||
match pty.spawn_terminal(terminal_action, client_or_tab_index) {
|
||||
Ok(pid) => {
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::NewPane(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
should_float,
|
||||
client_or_tab_index,
|
||||
))
|
||||
.unwrap();
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(pid)) => {
|
||||
if hold_on_close {
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::NewPane(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
should_float,
|
||||
client_or_tab_index,
|
||||
))
|
||||
.unwrap();
|
||||
if let Some(run_command) = run_command {
|
||||
send_command_not_found_to_screen(
|
||||
pty.bus.senders.clone(),
|
||||
pid,
|
||||
run_command.clone(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
pty.close_pane(PaneId::Terminal(pid));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to spawn terminal: {}", e);
|
||||
},
|
||||
}
|
||||
},
|
||||
PtyInstruction::OpenInPlaceEditor(temp_file, line_number, client_id) => {
|
||||
match pty.spawn_terminal(
|
||||
|
|
@ -107,28 +150,124 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) {
|
|||
}
|
||||
},
|
||||
PtyInstruction::SpawnTerminalVertically(terminal_action, client_id) => {
|
||||
let pid = pty
|
||||
.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id))
|
||||
.unwrap(); // TODO: handle error here
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::VerticalSplit(
|
||||
PaneId::Terminal(pid),
|
||||
client_id,
|
||||
))
|
||||
.unwrap();
|
||||
let (hold_on_close, run_command, pane_title) = match &terminal_action {
|
||||
Some(TerminalAction::RunCommand(run_command)) => (
|
||||
run_command.hold_on_close,
|
||||
Some(run_command.clone()),
|
||||
Some(run_command.to_string()),
|
||||
),
|
||||
_ => (false, None, None),
|
||||
};
|
||||
match pty.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) {
|
||||
Ok(pid) => {
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::VerticalSplit(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
client_id,
|
||||
))
|
||||
.unwrap();
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(pid)) => {
|
||||
if hold_on_close {
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::VerticalSplit(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
client_id,
|
||||
))
|
||||
.unwrap();
|
||||
if let Some(run_command) = run_command {
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::PtyBytes(
|
||||
pid,
|
||||
format!(
|
||||
"Command not found: {}",
|
||||
run_command.command.display()
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
))
|
||||
.unwrap();
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HoldPane(
|
||||
PaneId::Terminal(pid),
|
||||
Some(2), // exit status
|
||||
run_command,
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to spawn terminal: {}", e);
|
||||
},
|
||||
}
|
||||
},
|
||||
PtyInstruction::SpawnTerminalHorizontally(terminal_action, client_id) => {
|
||||
let pid = pty
|
||||
.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id))
|
||||
.unwrap(); // TODO: handle error here
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HorizontalSplit(
|
||||
PaneId::Terminal(pid),
|
||||
client_id,
|
||||
))
|
||||
.unwrap();
|
||||
let (hold_on_close, run_command, pane_title) = match &terminal_action {
|
||||
Some(TerminalAction::RunCommand(run_command)) => (
|
||||
run_command.hold_on_close,
|
||||
Some(run_command.clone()),
|
||||
Some(run_command.to_string()),
|
||||
),
|
||||
_ => (false, None, None),
|
||||
};
|
||||
match pty.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) {
|
||||
Ok(pid) => {
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HorizontalSplit(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
client_id,
|
||||
))
|
||||
.unwrap();
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(pid)) => {
|
||||
if hold_on_close {
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HorizontalSplit(
|
||||
PaneId::Terminal(pid),
|
||||
pane_title,
|
||||
client_id,
|
||||
))
|
||||
.unwrap();
|
||||
if let Some(run_command) = run_command {
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::PtyBytes(
|
||||
pid,
|
||||
format!(
|
||||
"Command not found: {}",
|
||||
run_command.command.display()
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
))
|
||||
.unwrap();
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HoldPane(
|
||||
PaneId::Terminal(pid),
|
||||
Some(2), // exit status
|
||||
run_command,
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to spawn terminal: {}", e);
|
||||
},
|
||||
}
|
||||
},
|
||||
PtyInstruction::UpdateActivePane(pane_id, client_id) => {
|
||||
pty.set_active_pane(pane_id, client_id);
|
||||
|
|
@ -175,6 +314,36 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) {
|
|||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
},
|
||||
PtyInstruction::ReRunCommandInPane(pane_id, run_command) => {
|
||||
match pty.rerun_command_in_pane(pane_id, run_command.clone()) {
|
||||
Ok(..) => {},
|
||||
Err(SpawnTerminalError::CommandNotFound(pid)) => {
|
||||
if run_command.hold_on_close {
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::PtyBytes(
|
||||
pid,
|
||||
format!("Command not found: {}", run_command.command.display())
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
))
|
||||
.unwrap();
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HoldPane(
|
||||
PaneId::Terminal(pid),
|
||||
Some(2), // exit status
|
||||
run_command,
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to spawn terminal: {}", e);
|
||||
},
|
||||
}
|
||||
},
|
||||
PtyInstruction::Exit => break,
|
||||
}
|
||||
}
|
||||
|
|
@ -200,6 +369,7 @@ impl Pty {
|
|||
args: vec![],
|
||||
command: PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")),
|
||||
cwd: None, // this should be filled by the calling function, eg. spawn_terminal
|
||||
hold_on_close: false,
|
||||
})
|
||||
}
|
||||
fn fill_cwd(&self, terminal_action: &mut TerminalAction, client_id: ClientId) {
|
||||
|
|
@ -225,7 +395,8 @@ impl Pty {
|
|||
&mut self,
|
||||
terminal_action: Option<TerminalAction>,
|
||||
client_or_tab_index: ClientOrTabIndex,
|
||||
) -> Result<RawFd, &'static str> {
|
||||
) -> Result<u32, SpawnTerminalError> {
|
||||
// returns the terminal id
|
||||
let terminal_action = match client_or_tab_index {
|
||||
ClientOrTabIndex::ClientId(client_id) => {
|
||||
let mut terminal_action =
|
||||
|
|
@ -237,13 +408,26 @@ impl Pty {
|
|||
terminal_action.unwrap_or_else(|| self.get_default_terminal())
|
||||
},
|
||||
};
|
||||
let hold_on_close = match &terminal_action {
|
||||
TerminalAction::RunCommand(run_command) => run_command.hold_on_close,
|
||||
_ => false,
|
||||
};
|
||||
let quit_cb = Box::new({
|
||||
let senders = self.bus.senders.clone();
|
||||
move |pane_id| {
|
||||
let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
|
||||
move |pane_id, exit_status, command| {
|
||||
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
|
||||
let (terminal_id, pid_primary, child_fd): (u32, RawFd, RawFd) = self
|
||||
.bus
|
||||
.os_input
|
||||
.as_mut()
|
||||
|
|
@ -254,15 +438,15 @@ impl Pty {
|
|||
let os_input = self.bus.os_input.as_ref().unwrap().clone();
|
||||
let debug_to_file = self.debug_to_file;
|
||||
async move {
|
||||
TerminalBytes::new(pid_primary, senders, os_input, debug_to_file)
|
||||
TerminalBytes::new(pid_primary, senders, os_input, debug_to_file, terminal_id)
|
||||
.listen()
|
||||
.await;
|
||||
}
|
||||
});
|
||||
|
||||
self.task_handles.insert(pid_primary, terminal_bytes);
|
||||
self.id_to_child_pid.insert(pid_primary, child_fd);
|
||||
Ok(pid_primary)
|
||||
self.task_handles.insert(terminal_id, terminal_bytes);
|
||||
self.id_to_child_pid.insert(terminal_id, child_fd);
|
||||
Ok(terminal_id)
|
||||
}
|
||||
pub fn spawn_terminals_for_layout(
|
||||
&mut self,
|
||||
|
|
@ -273,77 +457,155 @@ impl Pty {
|
|||
let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal());
|
||||
self.fill_cwd(&mut default_shell, client_id);
|
||||
let extracted_run_instructions = layout.extract_run_instructions();
|
||||
let mut new_pane_pids = vec![];
|
||||
let mut new_pane_pids: Vec<(u32, Option<RunCommand>, Result<RawFd, SpawnTerminalError>)> =
|
||||
vec![]; // (terminal_id,
|
||||
// run_command
|
||||
// file_descriptor)
|
||||
for run_instruction in extracted_run_instructions {
|
||||
let quit_cb = Box::new({
|
||||
let senders = self.bus.senders.clone();
|
||||
move |pane_id| {
|
||||
move |pane_id, _exit_status, _command| {
|
||||
let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None));
|
||||
}
|
||||
});
|
||||
match run_instruction {
|
||||
Some(Run::Command(command)) => {
|
||||
let cmd = TerminalAction::RunCommand(command);
|
||||
let (pid_primary, child_fd): (RawFd, RawFd) = self
|
||||
.bus
|
||||
.os_input
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.spawn_terminal(cmd, quit_cb, self.default_editor.clone())
|
||||
.unwrap(); // TODO: handle error here
|
||||
self.id_to_child_pid.insert(pid_primary, child_fd);
|
||||
new_pane_pids.push(pid_primary);
|
||||
let hold_on_close = command.hold_on_close;
|
||||
let quit_cb = Box::new({
|
||||
let senders = self.bus.senders.clone();
|
||||
move |pane_id, exit_status, command| {
|
||||
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 cmd = TerminalAction::RunCommand(command.clone());
|
||||
match self.bus.os_input.as_mut().unwrap().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);
|
||||
},
|
||||
}
|
||||
},
|
||||
None => {
|
||||
let (pid_primary, child_fd): (RawFd, RawFd) = self
|
||||
.bus
|
||||
.os_input
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.spawn_terminal(default_shell.clone(), quit_cb, self.default_editor.clone())
|
||||
.unwrap(); // TODO: handle error here
|
||||
self.id_to_child_pid.insert(pid_primary, child_fd);
|
||||
new_pane_pids.push(pid_primary);
|
||||
match self.bus.os_input.as_mut().unwrap().spawn_terminal(
|
||||
default_shell.clone(),
|
||||
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, None, Ok(pid_primary)));
|
||||
},
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)) => {
|
||||
new_pane_pids.push((
|
||||
terminal_id,
|
||||
None,
|
||||
Err(SpawnTerminalError::CommandNotFound(terminal_id)),
|
||||
));
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to spawn terminal: {}", e);
|
||||
},
|
||||
}
|
||||
},
|
||||
// Investigate moving plugin loading to here.
|
||||
Some(Run::Plugin(_)) => {},
|
||||
}
|
||||
}
|
||||
let new_tab_pane_ids: Vec<u32> = new_pane_pids
|
||||
.iter()
|
||||
.map(|(terminal_id, _, _)| *terminal_id)
|
||||
.collect::<Vec<u32>>();
|
||||
self.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::NewTab(
|
||||
layout,
|
||||
new_pane_pids.clone(),
|
||||
new_tab_pane_ids,
|
||||
client_id,
|
||||
))
|
||||
.unwrap();
|
||||
for id in new_pane_pids {
|
||||
let terminal_bytes = task::spawn({
|
||||
let senders = self.bus.senders.clone();
|
||||
let os_input = self.bus.os_input.as_ref().unwrap().clone();
|
||||
let debug_to_file = self.debug_to_file;
|
||||
async move {
|
||||
TerminalBytes::new(id, senders, os_input, debug_to_file)
|
||||
.listen()
|
||||
.await;
|
||||
}
|
||||
});
|
||||
self.task_handles.insert(id, terminal_bytes);
|
||||
for (terminal_id, run_command, pid_primary) in new_pane_pids {
|
||||
match pid_primary {
|
||||
Ok(pid_primary) => {
|
||||
let terminal_bytes = task::spawn({
|
||||
let senders = self.bus.senders.clone();
|
||||
let os_input = self.bus.os_input.as_ref().unwrap().clone();
|
||||
let debug_to_file = self.debug_to_file;
|
||||
async move {
|
||||
TerminalBytes::new(
|
||||
pid_primary,
|
||||
senders,
|
||||
os_input,
|
||||
debug_to_file,
|
||||
terminal_id,
|
||||
)
|
||||
.listen()
|
||||
.await;
|
||||
}
|
||||
});
|
||||
self.task_handles.insert(terminal_id, terminal_bytes);
|
||||
},
|
||||
_ => match run_command {
|
||||
Some(run_command) => {
|
||||
if run_command.hold_on_close {
|
||||
send_command_not_found_to_screen(
|
||||
self.bus.senders.clone(),
|
||||
terminal_id,
|
||||
run_command.clone(),
|
||||
);
|
||||
} else {
|
||||
self.close_pane(PaneId::Terminal(terminal_id));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
self.close_pane(PaneId::Terminal(terminal_id));
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn close_pane(&mut self, id: PaneId) {
|
||||
match id {
|
||||
PaneId::Terminal(id) => {
|
||||
let child_fd = self.id_to_child_pid.remove(&id).unwrap();
|
||||
self.task_handles.remove(&id).unwrap();
|
||||
task::block_on(async {
|
||||
self.bus
|
||||
.os_input
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.kill(Pid::from_raw(child_fd))
|
||||
.unwrap();
|
||||
});
|
||||
self.task_handles.remove(&id);
|
||||
if let Some(child_fd) = self.id_to_child_pid.remove(&id) {
|
||||
task::block_on(async {
|
||||
self.bus
|
||||
.os_input
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.kill(Pid::from_raw(child_fd))
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
self.bus.os_input.as_ref().unwrap().clear_terminal_id(id);
|
||||
},
|
||||
PaneId::Plugin(pid) => drop(
|
||||
self.bus
|
||||
|
|
@ -362,13 +624,84 @@ impl Pty {
|
|||
self.active_panes.insert(client_id, pane_id);
|
||||
}
|
||||
}
|
||||
pub fn rerun_command_in_pane(
|
||||
&mut self,
|
||||
pane_id: PaneId,
|
||||
run_command: RunCommand,
|
||||
) -> Result<(), SpawnTerminalError> {
|
||||
match pane_id {
|
||||
PaneId::Terminal(id) => {
|
||||
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 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,
|
||||
));
|
||||
}
|
||||
});
|
||||
let (pid_primary, child_fd): (RawFd, RawFd) =
|
||||
self.bus
|
||||
.os_input
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.re_run_command_in_terminal(id, run_command, quit_cb)?;
|
||||
let terminal_bytes = task::spawn({
|
||||
let senders = self.bus.senders.clone();
|
||||
let os_input = self.bus.os_input.as_ref().unwrap().clone();
|
||||
let debug_to_file = self.debug_to_file;
|
||||
async move {
|
||||
TerminalBytes::new(pid_primary, senders, os_input, debug_to_file, id)
|
||||
.listen()
|
||||
.await;
|
||||
}
|
||||
});
|
||||
|
||||
self.task_handles.insert(id, terminal_bytes);
|
||||
self.id_to_child_pid.insert(id, child_fd);
|
||||
Ok(())
|
||||
},
|
||||
_ => Err(SpawnTerminalError::GenericSpawnError(
|
||||
"Cannot respawn plugin panes",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Pty {
|
||||
fn drop(&mut self) {
|
||||
let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect();
|
||||
let child_ids: Vec<u32> = self.id_to_child_pid.keys().copied().collect();
|
||||
for id in child_ids {
|
||||
self.close_pane(PaneId::Terminal(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_command_not_found_to_screen(
|
||||
senders: ThreadSenders,
|
||||
terminal_id: u32,
|
||||
run_command: RunCommand,
|
||||
) {
|
||||
senders
|
||||
.send_to_screen(ScreenInstruction::PtyBytes(
|
||||
terminal_id,
|
||||
format!("Command not found: {}", run_command.command.display())
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
))
|
||||
.unwrap();
|
||||
senders
|
||||
.send_to_screen(ScreenInstruction::HoldPane(
|
||||
PaneId::Terminal(terminal_id),
|
||||
Some(2),
|
||||
run_command.clone(),
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::thread_bus::Bus;
|
|||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub(crate) enum PtyWriteInstruction {
|
||||
Write(Vec<u8>, i32),
|
||||
Write(Vec<u8>, u32),
|
||||
Exit,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -245,29 +245,17 @@ pub(crate) fn route_action(
|
|||
PtyInstruction::SpawnTerminalHorizontally(shell, client_id)
|
||||
},
|
||||
// No direction specified - try to put it in the biggest available spot
|
||||
None => PtyInstruction::SpawnTerminal(shell, ClientOrTabIndex::ClientId(client_id)),
|
||||
None => PtyInstruction::SpawnTerminal(
|
||||
shell,
|
||||
None,
|
||||
ClientOrTabIndex::ClientId(client_id),
|
||||
),
|
||||
};
|
||||
session.senders.send_to_pty(pty_instr).unwrap();
|
||||
},
|
||||
Action::EditFile(path_to_file, line_number, split_direction, should_float) => {
|
||||
match should_float {
|
||||
Some(true) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ShowFloatingPanes(client_id))
|
||||
.unwrap();
|
||||
},
|
||||
Some(false) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HideFloatingPanes(client_id))
|
||||
.unwrap();
|
||||
},
|
||||
None => {},
|
||||
};
|
||||
|
||||
let open_file = TerminalAction::OpenFile(path_to_file, line_number);
|
||||
let pty_instr = match (split_direction, should_float.unwrap_or(false)) {
|
||||
let pty_instr = match (split_direction, should_float) {
|
||||
(Some(Direction::Left), false) => {
|
||||
PtyInstruction::SpawnTerminalVertically(Some(open_file), client_id)
|
||||
},
|
||||
|
|
@ -283,6 +271,7 @@ pub(crate) fn route_action(
|
|||
// No direction specified or should float - defer placement to screen
|
||||
(None, _) | (_, true) => PtyInstruction::SpawnTerminal(
|
||||
Some(open_file),
|
||||
Some(should_float),
|
||||
ClientOrTabIndex::ClientId(client_id),
|
||||
),
|
||||
};
|
||||
|
|
@ -308,10 +297,7 @@ pub(crate) fn route_action(
|
|||
.unwrap();
|
||||
},
|
||||
Action::NewFloatingPane(run_command) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ShowFloatingPanes(client_id))
|
||||
.unwrap();
|
||||
let should_float = true;
|
||||
let run_cmd = run_command
|
||||
.map(|cmd| TerminalAction::RunCommand(cmd.into()))
|
||||
.or_else(|| session.default_shell.clone());
|
||||
|
|
@ -319,15 +305,13 @@ pub(crate) fn route_action(
|
|||
.senders
|
||||
.send_to_pty(PtyInstruction::SpawnTerminal(
|
||||
run_cmd,
|
||||
Some(should_float),
|
||||
ClientOrTabIndex::ClientId(client_id),
|
||||
))
|
||||
.unwrap();
|
||||
},
|
||||
Action::NewTiledPane(direction, run_command) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HideFloatingPanes(client_id))
|
||||
.unwrap();
|
||||
let should_float = false;
|
||||
let run_cmd = run_command
|
||||
.map(|cmd| TerminalAction::RunCommand(cmd.into()))
|
||||
.or_else(|| session.default_shell.clone());
|
||||
|
|
@ -345,9 +329,11 @@ pub(crate) fn route_action(
|
|||
PtyInstruction::SpawnTerminalHorizontally(run_cmd, client_id)
|
||||
},
|
||||
// No direction specified - try to put it in the biggest available spot
|
||||
None => {
|
||||
PtyInstruction::SpawnTerminal(run_cmd, ClientOrTabIndex::ClientId(client_id))
|
||||
},
|
||||
None => PtyInstruction::SpawnTerminal(
|
||||
run_cmd,
|
||||
Some(should_float),
|
||||
ClientOrTabIndex::ClientId(client_id),
|
||||
),
|
||||
};
|
||||
session.senders.send_to_pty(pty_instr).unwrap();
|
||||
},
|
||||
|
|
@ -394,9 +380,11 @@ pub(crate) fn route_action(
|
|||
PtyInstruction::SpawnTerminalHorizontally(run_cmd, client_id)
|
||||
},
|
||||
// No direction specified - try to put it in the biggest available spot
|
||||
None => {
|
||||
PtyInstruction::SpawnTerminal(run_cmd, ClientOrTabIndex::ClientId(client_id))
|
||||
},
|
||||
None => PtyInstruction::SpawnTerminal(
|
||||
run_cmd,
|
||||
None,
|
||||
ClientOrTabIndex::ClientId(client_id),
|
||||
),
|
||||
};
|
||||
session.senders.send_to_pty(pty_instr).unwrap();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::rc::Rc;
|
||||
use std::str;
|
||||
|
||||
use zellij_utils::errors::prelude::*;
|
||||
use zellij_utils::input::command::RunCommand;
|
||||
use zellij_utils::input::options::Clipboard;
|
||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||
use zellij_utils::{input::command::TerminalAction, input::layout::PaneLayout, position::Position};
|
||||
|
|
@ -115,16 +115,16 @@ macro_rules! active_tab_and_connected_client_id {
|
|||
/// Instructions that can be sent to the [`Screen`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ScreenInstruction {
|
||||
PtyBytes(RawFd, VteBytes),
|
||||
PtyBytes(u32, VteBytes),
|
||||
Render,
|
||||
NewPane(PaneId, ClientOrTabIndex),
|
||||
NewPane(PaneId, Option<String>, Option<bool>, ClientOrTabIndex), // String is initial title,
|
||||
// bool (if Some) is
|
||||
// should_float
|
||||
OpenInPlaceEditor(PaneId, ClientId),
|
||||
TogglePaneEmbedOrFloating(ClientId),
|
||||
ToggleFloatingPanes(ClientId, Option<TerminalAction>),
|
||||
ShowFloatingPanes(ClientId),
|
||||
HideFloatingPanes(ClientId),
|
||||
HorizontalSplit(PaneId, ClientId),
|
||||
VerticalSplit(PaneId, ClientId),
|
||||
HorizontalSplit(PaneId, Option<String>, ClientId), // String is initial title
|
||||
VerticalSplit(PaneId, Option<String>, ClientId), // String is initial title
|
||||
WriteCharacter(Vec<u8>, ClientId),
|
||||
ResizeLeft(ClientId),
|
||||
ResizeRight(ClientId),
|
||||
|
|
@ -164,9 +164,10 @@ pub enum ScreenInstruction {
|
|||
TogglePaneFrames,
|
||||
SetSelectable(PaneId, bool, usize),
|
||||
ClosePane(PaneId, Option<ClientId>),
|
||||
HoldPane(PaneId, Option<i32>, RunCommand, Option<ClientId>), // Option<i32> is the exit status
|
||||
UpdatePaneName(Vec<u8>, ClientId),
|
||||
UndoRenamePane(ClientId),
|
||||
NewTab(PaneLayout, Vec<RawFd>, ClientId),
|
||||
NewTab(PaneLayout, Vec<u32>, ClientId),
|
||||
SwitchTabNext(ClientId),
|
||||
SwitchTabPrev(ClientId),
|
||||
ToggleActiveSyncTab(ClientId),
|
||||
|
|
@ -217,8 +218,6 @@ impl From<&ScreenInstruction> for ScreenContext {
|
|||
ScreenContext::TogglePaneEmbedOrFloating
|
||||
},
|
||||
ScreenInstruction::ToggleFloatingPanes(..) => ScreenContext::ToggleFloatingPanes,
|
||||
ScreenInstruction::ShowFloatingPanes(..) => ScreenContext::ShowFloatingPanes,
|
||||
ScreenInstruction::HideFloatingPanes(..) => ScreenContext::HideFloatingPanes,
|
||||
ScreenInstruction::HorizontalSplit(..) => ScreenContext::HorizontalSplit,
|
||||
ScreenInstruction::VerticalSplit(..) => ScreenContext::VerticalSplit,
|
||||
ScreenInstruction::WriteCharacter(..) => ScreenContext::WriteCharacter,
|
||||
|
|
@ -264,6 +263,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
|||
ScreenInstruction::TogglePaneFrames => ScreenContext::TogglePaneFrames,
|
||||
ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable,
|
||||
ScreenInstruction::ClosePane(..) => ScreenContext::ClosePane,
|
||||
ScreenInstruction::HoldPane(..) => ScreenContext::HoldPane,
|
||||
ScreenInstruction::UpdatePaneName(..) => ScreenContext::UpdatePaneName,
|
||||
ScreenInstruction::UndoRenamePane(..) => ScreenContext::UndoRenamePane,
|
||||
ScreenInstruction::NewTab(..) => ScreenContext::NewTab,
|
||||
|
|
@ -811,7 +811,7 @@ impl Screen {
|
|||
pub fn new_tab(
|
||||
&mut self,
|
||||
layout: PaneLayout,
|
||||
new_pids: Vec<RawFd>,
|
||||
new_ids: Vec<u32>,
|
||||
client_id: ClientId,
|
||||
) -> Result<()> {
|
||||
let client_id = if self.get_active_tab(client_id).is_some() {
|
||||
|
|
@ -821,7 +821,7 @@ impl Screen {
|
|||
} else {
|
||||
client_id
|
||||
};
|
||||
let err_context = || format!("failed to create new tab for client {client_id:?}");
|
||||
let err_context = || format!("failed to create new tab for client {client_id:?}",);
|
||||
let tab_index = self.get_new_tab_index();
|
||||
let position = self.tabs.len();
|
||||
let mut tab = Tab::new(
|
||||
|
|
@ -848,7 +848,7 @@ impl Screen {
|
|||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
);
|
||||
tab.apply_layout(layout, new_pids, tab_index, client_id)
|
||||
tab.apply_layout(layout, new_ids, tab_index, client_id)
|
||||
.with_context(err_context)?;
|
||||
if self.session_is_mirrored {
|
||||
if let Some(active_tab) = self.get_active_tab_mut(client_id) {
|
||||
|
|
@ -1002,9 +1002,8 @@ impl Screen {
|
|||
}
|
||||
},
|
||||
}
|
||||
self.update_tabs().with_context(|| {
|
||||
format!("failed to update active tabs name for client id: {client_id:?}")
|
||||
})
|
||||
self.update_tabs()
|
||||
.context("failed to update active tabs name for client id: {client_id:?}")
|
||||
} else {
|
||||
log::error!("Active tab not found for client id: {client_id:?}");
|
||||
Ok(())
|
||||
|
|
@ -1092,7 +1091,6 @@ impl Screen {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn change_mode_for_all_clients(&mut self, mode_info: ModeInfo) -> Result<()> {
|
||||
let err_context = || {
|
||||
format!(
|
||||
|
|
@ -1225,17 +1223,24 @@ pub(crate) fn screen_thread_main(
|
|||
ScreenInstruction::Render => {
|
||||
screen.render()?;
|
||||
},
|
||||
ScreenInstruction::NewPane(pid, client_or_tab_index) => {
|
||||
ScreenInstruction::NewPane(
|
||||
pid,
|
||||
initial_pane_title,
|
||||
should_float,
|
||||
client_or_tab_index,
|
||||
) => {
|
||||
match client_or_tab_index {
|
||||
ClientOrTabIndex::ClientId(client_id) => {
|
||||
active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab,
|
||||
client_id: ClientId| tab .new_pane(pid,
|
||||
initial_pane_title,
|
||||
should_float,
|
||||
Some(client_id)),
|
||||
?);
|
||||
},
|
||||
ClientOrTabIndex::TabIndex(tab_index) => {
|
||||
if let Some(active_tab) = screen.tabs.get_mut(&tab_index) {
|
||||
active_tab.new_pane(pid, None)?;
|
||||
active_tab.new_pane(pid, initial_pane_title, should_float, None)?;
|
||||
} else {
|
||||
log::error!("Tab index not found: {:?}", tab_index);
|
||||
}
|
||||
|
|
@ -1270,44 +1275,22 @@ pub(crate) fn screen_thread_main(
|
|||
|
||||
screen.render()?;
|
||||
},
|
||||
ScreenInstruction::ShowFloatingPanes(client_id) => {
|
||||
ScreenInstruction::HorizontalSplit(pid, initial_pane_title, client_id) => {
|
||||
active_tab_and_connected_client_id!(
|
||||
screen,
|
||||
client_id,
|
||||
|tab: &mut Tab, _client_id: ClientId| tab.show_floating_panes()
|
||||
);
|
||||
screen.unblock_input()?;
|
||||
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
|
||||
|
||||
screen.render()?;
|
||||
},
|
||||
ScreenInstruction::HideFloatingPanes(client_id) => {
|
||||
active_tab_and_connected_client_id!(
|
||||
screen,
|
||||
client_id,
|
||||
|tab: &mut Tab, _client_id: ClientId| tab.hide_floating_panes()
|
||||
);
|
||||
screen.unblock_input()?;
|
||||
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
|
||||
|
||||
screen.render()?;
|
||||
},
|
||||
ScreenInstruction::HorizontalSplit(pid, client_id) => {
|
||||
active_tab_and_connected_client_id!(
|
||||
screen,
|
||||
client_id,
|
||||
|tab: &mut Tab, client_id: ClientId| tab.horizontal_split(pid, client_id),
|
||||
|tab: &mut Tab, client_id: ClientId| tab.horizontal_split(pid, initial_pane_title, client_id),
|
||||
?
|
||||
);
|
||||
screen.unblock_input()?;
|
||||
screen.update_tabs()?;
|
||||
screen.render()?;
|
||||
},
|
||||
ScreenInstruction::VerticalSplit(pid, client_id) => {
|
||||
ScreenInstruction::VerticalSplit(pid, initial_pane_title, client_id) => {
|
||||
active_tab_and_connected_client_id!(
|
||||
screen,
|
||||
client_id,
|
||||
|tab: &mut Tab, client_id: ClientId| tab.vertical_split(pid, client_id),
|
||||
|tab: &mut Tab, client_id: ClientId| tab.vertical_split(pid, initial_pane_title, client_id),
|
||||
?
|
||||
);
|
||||
screen.unblock_input()?;
|
||||
|
|
@ -1657,6 +1640,27 @@ pub(crate) fn screen_thread_main(
|
|||
screen.update_tabs()?;
|
||||
screen.unblock_input()?;
|
||||
},
|
||||
ScreenInstruction::HoldPane(id, exit_status, run_command, client_id) => {
|
||||
match client_id {
|
||||
Some(client_id) => {
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| tab.hold_pane(
|
||||
id,
|
||||
exit_status,
|
||||
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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
screen.update_tabs()?;
|
||||
screen.unblock_input()?;
|
||||
},
|
||||
ScreenInstruction::UpdatePaneName(c, client_id) => {
|
||||
active_tab_and_connected_client_id!(
|
||||
screen,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use copy_command::CopyCommand;
|
|||
use std::env::temp_dir;
|
||||
use uuid::Uuid;
|
||||
use zellij_utils::errors::prelude::*;
|
||||
use zellij_utils::input::command::RunCommand;
|
||||
use zellij_utils::position::{Column, Line};
|
||||
use zellij_utils::{position::Position, serde};
|
||||
|
||||
|
|
@ -29,7 +30,6 @@ use crate::{
|
|||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Instant;
|
||||
|
|
@ -50,9 +50,9 @@ use zellij_utils::{
|
|||
macro_rules! resize_pty {
|
||||
($pane:expr, $os_input:expr) => {
|
||||
if let PaneId::Terminal(ref pid) = $pane.pid() {
|
||||
// FIXME: This `set_terminal_size_using_fd` call would be best in
|
||||
// FIXME: This `set_terminal_size_using_terminal_id` call would be best in
|
||||
// `TerminalPane::reflow_lines`
|
||||
$os_input.set_terminal_size_using_fd(
|
||||
$os_input.set_terminal_size_using_terminal_id(
|
||||
*pid,
|
||||
$pane.get_content_columns() as u16,
|
||||
$pane.get_content_rows() as u16,
|
||||
|
|
@ -89,7 +89,7 @@ pub(crate) struct Tab {
|
|||
pub style: Style,
|
||||
connected_clients: Rc<RefCell<HashSet<ClientId>>>,
|
||||
draw_pane_frames: bool,
|
||||
pending_vte_events: HashMap<RawFd, Vec<VteBytes>>,
|
||||
pending_vte_events: HashMap<u32, Vec<VteBytes>>,
|
||||
pub selecting_with_mouse: bool, // this is only pub for the tests TODO: remove this once we combine write_text_to_clipboard with render
|
||||
link_handler: Rc<RefCell<LinkHandler>>,
|
||||
clipboard_provider: ClipboardProvider,
|
||||
|
|
@ -100,6 +100,7 @@ pub(crate) struct Tab {
|
|||
last_mouse_hold_position: Option<Position>,
|
||||
terminal_emulator_colors: Rc<RefCell<Palette>>,
|
||||
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
|
||||
pids_waiting_resize: HashSet<u32>, // u32 is the terminal_id
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
|
|
@ -127,7 +128,9 @@ pub trait Pane {
|
|||
fn set_geom_override(&mut self, pane_geom: PaneGeom);
|
||||
fn handle_pty_bytes(&mut self, bytes: VteBytes);
|
||||
fn cursor_coordinates(&self) -> Option<(usize, usize)>;
|
||||
fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8>;
|
||||
fn adjust_input_to_terminal(&mut self, _input_bytes: Vec<u8>) -> Option<AdjustedInput> {
|
||||
None
|
||||
}
|
||||
fn position_and_size(&self) -> PaneGeom;
|
||||
fn current_geom(&self) -> PaneGeom;
|
||||
fn geom_override(&self) -> Option<PaneGeom>;
|
||||
|
|
@ -351,6 +354,16 @@ pub trait Pane {
|
|||
// False by default (only terminal-panes support alternate mode)
|
||||
false
|
||||
}
|
||||
fn hold(&mut self, _exit_status: Option<i32>, _run_command: RunCommand) {
|
||||
// No-op by default, only terminal panes support holding
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AdjustedInput {
|
||||
WriteBytesToTerminal(Vec<u8>),
|
||||
ReRunCommandInThisPane(RunCommand),
|
||||
CloseThisPane,
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
|
|
@ -450,13 +463,14 @@ impl Tab {
|
|||
last_mouse_hold_position: None,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
pids_waiting_resize: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_layout(
|
||||
&mut self,
|
||||
layout: PaneLayout,
|
||||
new_pids: Vec<RawFd>,
|
||||
new_ids: Vec<u32>,
|
||||
tab_index: usize,
|
||||
client_id: ClientId,
|
||||
) -> Result<()> {
|
||||
|
|
@ -481,7 +495,7 @@ impl Tab {
|
|||
let positions_in_layout = layout.position_panes_in_space(&free_space);
|
||||
|
||||
let positions_and_size = positions_in_layout.iter();
|
||||
let mut new_pids = new_pids.iter();
|
||||
let mut new_ids = new_ids.iter();
|
||||
|
||||
let mut focus_pane_id: Option<PaneId> = None;
|
||||
let mut set_focus_pane_id = |layout: &PaneLayout, pane_id: PaneId| {
|
||||
|
|
@ -516,27 +530,33 @@ 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
|
||||
let pid = new_pids.next().with_context(err_context)?; // if this crashes it means we got less pids than there are panes in this layout
|
||||
let next_terminal_position = self.get_next_terminal_position();
|
||||
let mut new_pane = TerminalPane::new(
|
||||
*pid,
|
||||
*position_and_size,
|
||||
self.style,
|
||||
next_terminal_position,
|
||||
layout.name.clone().unwrap_or_default(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
);
|
||||
new_pane.set_borderless(layout.borderless);
|
||||
self.tiled_panes
|
||||
.add_pane_with_existing_geom(PaneId::Terminal(*pid), Box::new(new_pane));
|
||||
set_focus_pane_id(layout, PaneId::Terminal(*pid));
|
||||
if let Some(pid) = 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()),
|
||||
_ => None,
|
||||
};
|
||||
let mut new_pane = TerminalPane::new(
|
||||
*pid,
|
||||
*position_and_size,
|
||||
self.style,
|
||||
next_terminal_position,
|
||||
layout.name.clone().unwrap_or_default(),
|
||||
self.link_handler.clone(),
|
||||
self.character_cell_size.clone(),
|
||||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
initial_title,
|
||||
);
|
||||
new_pane.set_borderless(layout.borderless);
|
||||
self.tiled_panes
|
||||
.add_pane_with_existing_geom(PaneId::Terminal(*pid), Box::new(new_pane));
|
||||
set_focus_pane_id(layout, PaneId::Terminal(*pid));
|
||||
}
|
||||
}
|
||||
}
|
||||
for unused_pid in new_pids {
|
||||
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
|
||||
|
|
@ -745,18 +765,10 @@ impl Tab {
|
|||
}
|
||||
},
|
||||
None => {
|
||||
// there aren't any floating panes, we need to open a new one
|
||||
//
|
||||
// ************************************************************************************************
|
||||
// BEWARE - THIS IS NOT ATOMIC - this sends an instruction to the pty thread to open a new terminal
|
||||
// the pty thread will do its thing and eventually come back to the new_pane
|
||||
// method on this tab which will open a new floating pane because we just
|
||||
// toggled their visibility above us.
|
||||
// If the pty thread takes too long, weird things can happen...
|
||||
// ************************************************************************************************
|
||||
//
|
||||
let should_float = true;
|
||||
let instruction = PtyInstruction::SpawnTerminal(
|
||||
default_shell,
|
||||
Some(should_float),
|
||||
ClientOrTabIndex::ClientId(client_id),
|
||||
);
|
||||
self.senders.send_to_pty(instruction).with_context(|| {
|
||||
|
|
@ -769,18 +781,18 @@ impl Tab {
|
|||
self.set_force_render();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn show_floating_panes(&mut self) {
|
||||
self.floating_panes.toggle_show_panes(true);
|
||||
self.set_force_render();
|
||||
}
|
||||
|
||||
pub fn hide_floating_panes(&mut self) {
|
||||
self.floating_panes.toggle_show_panes(false);
|
||||
self.set_force_render();
|
||||
}
|
||||
|
||||
pub fn new_pane(&mut self, pid: PaneId, client_id: Option<ClientId>) -> Result<()> {
|
||||
pub fn new_pane(
|
||||
&mut self,
|
||||
pid: PaneId,
|
||||
initial_pane_title: Option<String>,
|
||||
should_float: Option<bool>,
|
||||
client_id: Option<ClientId>,
|
||||
) -> Result<()> {
|
||||
match should_float {
|
||||
Some(true) => self.floating_panes.toggle_show_panes(true),
|
||||
Some(false) => self.floating_panes.toggle_show_panes(false),
|
||||
None => {},
|
||||
};
|
||||
self.close_down_to_max_terminals()
|
||||
.with_context(|| format!("failed to create new pane with id {pid:?}"))?;
|
||||
if self.floating_panes.panes_are_visible() {
|
||||
|
|
@ -798,6 +810,7 @@ impl Tab {
|
|||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
initial_pane_title,
|
||||
);
|
||||
new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
|
||||
resize_pty!(new_pane, self.os_api);
|
||||
|
|
@ -823,6 +836,7 @@ impl Tab {
|
|||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
initial_pane_title,
|
||||
);
|
||||
self.tiled_panes.insert_pane(pid, Box::new(new_terminal));
|
||||
self.should_clear_display_before_rendering = true;
|
||||
|
|
@ -841,7 +855,7 @@ impl Tab {
|
|||
match pid {
|
||||
PaneId::Terminal(pid) => {
|
||||
let next_terminal_position = self.get_next_terminal_position(); // TODO: this is not accurate in this case
|
||||
let new_pane = TerminalPane::new(
|
||||
let mut new_pane = TerminalPane::new(
|
||||
pid,
|
||||
PaneGeom::default(), // the initial size will be set later
|
||||
self.style,
|
||||
|
|
@ -852,7 +866,11 @@ impl Tab {
|
|||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
None,
|
||||
);
|
||||
new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the
|
||||
// constructor so it won't be overrided
|
||||
// by the editor
|
||||
let replaced_pane = if self.floating_panes.panes_are_visible() {
|
||||
self.floating_panes
|
||||
.replace_active_pane(Box::new(new_pane), client_id)
|
||||
|
|
@ -882,11 +900,14 @@ impl Tab {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn horizontal_split(&mut self, pid: PaneId, client_id: ClientId) -> Result<()> {
|
||||
pub fn horizontal_split(
|
||||
&mut self,
|
||||
pid: PaneId,
|
||||
initial_pane_title: Option<String>,
|
||||
client_id: ClientId,
|
||||
) -> Result<()> {
|
||||
let err_context =
|
||||
|| format!("failed to split pane {pid:?} horizontally for client {client_id}");
|
||||
|
||||
if self.floating_panes.panes_are_visible() {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -909,6 +930,7 @@ impl Tab {
|
|||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
initial_pane_title,
|
||||
);
|
||||
self.tiled_panes
|
||||
.split_pane_horizontally(pid, Box::new(new_terminal), client_id);
|
||||
|
|
@ -918,11 +940,14 @@ impl Tab {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vertical_split(&mut self, pid: PaneId, client_id: ClientId) -> Result<()> {
|
||||
pub fn vertical_split(
|
||||
&mut self,
|
||||
pid: PaneId,
|
||||
initial_pane_title: Option<String>,
|
||||
client_id: ClientId,
|
||||
) -> Result<()> {
|
||||
let err_context =
|
||||
|| format!("failed to split pane {pid:?} vertically for client {client_id}");
|
||||
|
||||
if self.floating_panes.panes_are_visible() {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -945,6 +970,7 @@ impl Tab {
|
|||
self.sixel_image_store.clone(),
|
||||
self.terminal_emulator_colors.clone(),
|
||||
self.terminal_emulator_color_codes.clone(),
|
||||
initial_pane_title,
|
||||
);
|
||||
self.tiled_panes
|
||||
.split_pane_vertically(pid, Box::new(new_terminal), client_id);
|
||||
|
|
@ -990,14 +1016,14 @@ impl Tab {
|
|||
self.tiled_panes.get_active_pane_id(client_id)
|
||||
}
|
||||
}
|
||||
fn get_active_terminal_id(&self, client_id: ClientId) -> Option<RawFd> {
|
||||
fn get_active_terminal_id(&self, client_id: ClientId) -> Option<u32> {
|
||||
if let Some(PaneId::Terminal(pid)) = self.get_active_pane_id(client_id) {
|
||||
Some(pid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn has_terminal_pid(&self, pid: RawFd) -> bool {
|
||||
pub fn has_terminal_pid(&self, pid: u32) -> bool {
|
||||
self.tiled_panes.panes_contain(&PaneId::Terminal(pid))
|
||||
|| self.floating_panes.panes_contain(&PaneId::Terminal(pid))
|
||||
|| self
|
||||
|
|
@ -1005,9 +1031,8 @@ impl Tab {
|
|||
.values()
|
||||
.any(|s_p| s_p.pid() == PaneId::Terminal(pid))
|
||||
}
|
||||
pub fn handle_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) -> Result<()> {
|
||||
pub fn handle_pty_bytes(&mut self, pid: u32, bytes: VteBytes) -> Result<()> {
|
||||
let err_context = || format!("failed to handle pty bytes from fd {pid}");
|
||||
|
||||
if let Some(terminal_output) = self
|
||||
.tiled_panes
|
||||
.get_pane_mut(PaneId::Terminal(pid))
|
||||
|
|
@ -1034,8 +1059,7 @@ impl Tab {
|
|||
}
|
||||
self.process_pty_bytes(pid, bytes).with_context(err_context)
|
||||
}
|
||||
|
||||
pub fn process_pending_vte_events(&mut self, pid: RawFd) -> Result<()> {
|
||||
pub fn process_pending_vte_events(&mut self, pid: u32) -> Result<()> {
|
||||
if let Some(pending_vte_events) = self.pending_vte_events.get_mut(&pid) {
|
||||
let vte_events: Vec<VteBytes> = pending_vte_events.drain(..).collect();
|
||||
for vte_event in vte_events {
|
||||
|
|
@ -1045,10 +1069,8 @@ impl Tab {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) -> Result<()> {
|
||||
fn process_pty_bytes(&mut self, pid: u32, bytes: VteBytes) -> Result<()> {
|
||||
let err_context = || format!("failed to process pty bytes from pid {pid}");
|
||||
|
||||
if let Some(terminal_output) = self
|
||||
.tiled_panes
|
||||
.get_pane_mut(PaneId::Terminal(pid))
|
||||
|
|
@ -1059,6 +1081,9 @@ impl Tab {
|
|||
.find(|s_p| s_p.pid() == PaneId::Terminal(pid))
|
||||
})
|
||||
{
|
||||
if self.pids_waiting_resize.remove(&pid) {
|
||||
resize_pty!(terminal_output, self.os_api);
|
||||
}
|
||||
terminal_output.handle_pty_bytes(bytes);
|
||||
let messages_to_pty = terminal_output.drain_messages_to_pty();
|
||||
let clipboard_update = terminal_output.drain_clipboard_update();
|
||||
|
|
@ -1149,19 +1174,34 @@ impl Tab {
|
|||
PaneId::Terminal(active_terminal_id) => {
|
||||
let active_terminal = self
|
||||
.floating_panes
|
||||
.get(&pane_id)
|
||||
.or_else(|| self.tiled_panes.get_pane(pane_id))
|
||||
.or_else(|| self.suppressed_panes.get(&pane_id))
|
||||
.get_mut(&pane_id)
|
||||
.or_else(|| self.tiled_panes.get_pane_mut(pane_id))
|
||||
.or_else(|| self.suppressed_panes.get_mut(&pane_id))
|
||||
.ok_or_else(|| anyhow!(format!("failed to find pane with id {pane_id:?}")))
|
||||
.with_context(err_context)?;
|
||||
let adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes);
|
||||
|
||||
self.senders
|
||||
.send_to_pty_writer(PtyWriteInstruction::Write(
|
||||
adjusted_input,
|
||||
active_terminal_id,
|
||||
))
|
||||
.with_context(err_context)?;
|
||||
match active_terminal.adjust_input_to_terminal(input_bytes) {
|
||||
Some(AdjustedInput::WriteBytesToTerminal(adjusted_input)) => {
|
||||
self.senders
|
||||
.send_to_pty_writer(PtyWriteInstruction::Write(
|
||||
adjusted_input,
|
||||
active_terminal_id,
|
||||
))
|
||||
.with_context(err_context)?;
|
||||
},
|
||||
Some(AdjustedInput::ReRunCommandInThisPane(command)) => {
|
||||
self.pids_waiting_resize.insert(active_terminal_id);
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ReRunCommandInPane(
|
||||
PaneId::Terminal(active_terminal_id),
|
||||
command,
|
||||
))
|
||||
.with_context(err_context)?;
|
||||
},
|
||||
Some(AdjustedInput::CloseThisPane) => {
|
||||
self.close_pane(PaneId::Terminal(active_terminal_id), false);
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
},
|
||||
PaneId::Plugin(pid) => {
|
||||
for key in parse_keys(&input_bytes) {
|
||||
|
|
@ -1686,6 +1726,13 @@ impl Tab {
|
|||
closed_pane
|
||||
}
|
||||
}
|
||||
pub fn hold_pane(&mut self, id: PaneId, exit_status: Option<i32>, run_command: RunCommand) {
|
||||
if self.floating_panes.panes_contain(&id) {
|
||||
self.floating_panes.hold_pane(id, exit_status, run_command);
|
||||
} else {
|
||||
self.tiled_panes.hold_pane(id, exit_status, run_command);
|
||||
}
|
||||
}
|
||||
pub fn replace_pane_with_suppressed_pane(&mut self, pane_id: PaneId) -> Option<Box<dyn Pane>> {
|
||||
self.suppressed_panes
|
||||
.remove(&pane_id)
|
||||
|
|
@ -2335,7 +2382,6 @@ impl Tab {
|
|||
let err_context = || {
|
||||
format!("failed to handle left mouse hold at position {position_on_screen:?} for client {client_id}")
|
||||
};
|
||||
|
||||
// return value indicates whether we should trigger a render
|
||||
// determine if event is repeated to enable smooth scrolling
|
||||
let is_repeated = if let Some(last_position) = self.last_mouse_hold_position {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
assertion_line: 1474
|
||||
assertion_line: 1801
|
||||
expression: snapshot
|
||||
---
|
||||
00 (C): ┌ Pane #1 ────────────────────┌ Pane #3 ─────────────────────────────────────────────────┐─────────┐
|
||||
00 (C): ┌ Pane #1 ────────────────────┌ EDITING SCROLLBACK ──────────────────────────────────────┐─────────┐
|
||||
01 (C): │ │ │ │
|
||||
02 (C): │ │ │ │
|
||||
03 (C): │ │ │ │
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
assertion_line: 1444
|
||||
assertion_line: 1771
|
||||
expression: snapshot
|
||||
---
|
||||
00 (C): ┌ Pane #2 ─────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
00 (C): ┌ EDITING SCROLLBACK ──────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
assertion_line: 1288
|
||||
assertion_line: 1639
|
||||
expression: snapshot
|
||||
---
|
||||
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
|
|
@ -8,7 +8,7 @@ expression: snapshot
|
|||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
04 (C): │ │
|
||||
05 (C): │ ┌ Pane #3 ─────────────────────────────────────────────────┐ │
|
||||
05 (C): │ ┌ EDITING SCROLLBACK ──────────────────────────────────────┐ │
|
||||
06 (C): │ │ │ │
|
||||
07 (C): │ │ │ │
|
||||
08 (C): │ │ │ │
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
|
||||
assertion_line: 1259
|
||||
assertion_line: 1613
|
||||
expression: snapshot
|
||||
---
|
||||
00 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
00 (C): ┌ EDITING SCROLLBACK ───────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
01 (C): │ │
|
||||
02 (C): │ │
|
||||
03 (C): │ │
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::screen::CopyOptions;
|
|||
use crate::Arc;
|
||||
use crate::Mutex;
|
||||
use crate::{
|
||||
os_input_output::{AsyncReader, Pid, ServerOsApi},
|
||||
os_input_output::{AsyncReader, Pid, ServerOsApi, SpawnTerminalError},
|
||||
panes::PaneId,
|
||||
thread_bus::ThreadSenders,
|
||||
ClientId,
|
||||
|
|
@ -30,7 +30,7 @@ use zellij_utils::nix;
|
|||
|
||||
use zellij_utils::{
|
||||
data::{InputMode, ModeInfo, Palette, Style},
|
||||
input::command::TerminalAction,
|
||||
input::command::{RunCommand, TerminalAction},
|
||||
interprocess::local_socket::LocalSocketStream,
|
||||
ipc::{ClientToServerMsg, ServerToClientMsg},
|
||||
};
|
||||
|
|
@ -41,15 +41,15 @@ struct FakeInputOutput {
|
|||
}
|
||||
|
||||
impl ServerOsApi for FakeInputOutput {
|
||||
fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) {
|
||||
fn set_terminal_size_using_terminal_id(&self, _id: u32, _cols: u16, _rows: u16) {
|
||||
// noop
|
||||
}
|
||||
fn spawn_terminal(
|
||||
&self,
|
||||
_file_to_open: TerminalAction,
|
||||
_quit_cb: Box<dyn Fn(PaneId) + Send>,
|
||||
_quit_db: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>,
|
||||
_default_editor: Option<PathBuf>,
|
||||
) -> Result<(RawFd, RawFd), &'static str> {
|
||||
) -> Result<(u32, RawFd, RawFd), SpawnTerminalError> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
|
|
@ -58,10 +58,10 @@ impl ServerOsApi for FakeInputOutput {
|
|||
fn async_file_reader(&self, _fd: RawFd) -> Box<dyn AsyncReader> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result<usize, nix::Error> {
|
||||
fn write_to_tty_stdin(&self, _id: u32, _buf: &[u8]) -> Result<usize, nix::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn tcdrain(&self, _fd: RawFd) -> Result<(), nix::Error> {
|
||||
fn tcdrain(&self, _id: u32) -> Result<(), nix::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn kill(&self, _pid: Pid) -> Result<(), nix::Error> {
|
||||
|
|
@ -103,6 +103,17 @@ impl ServerOsApi for FakeInputOutput {
|
|||
};
|
||||
self.file_dumps.lock().unwrap().insert(f, buf);
|
||||
}
|
||||
fn re_run_command_in_terminal(
|
||||
&self,
|
||||
_terminal_id: u32,
|
||||
_run_command: RunCommand,
|
||||
_quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status
|
||||
) -> Result<(RawFd, RawFd), SpawnTerminalError> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn clear_terminal_id(&self, _terminal_id: u32) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
struct MockPtyInstructionBus {
|
||||
|
|
@ -266,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 i32)
|
||||
.map(|(i, _)| i as u32)
|
||||
.collect();
|
||||
tab.apply_layout(tab_layout, pane_ids, index, client_id)
|
||||
.unwrap();
|
||||
|
|
@ -491,7 +502,8 @@ fn dump_screen() {
|
|||
file_dumps: map.clone(),
|
||||
});
|
||||
let new_pane_id = PaneId::Terminal(2);
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(2, Vec::from("scratch".as_bytes()))
|
||||
.unwrap();
|
||||
let file = "/tmp/log.sh";
|
||||
|
|
@ -514,7 +526,8 @@ fn new_floating_pane() {
|
|||
let new_pane_id = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -541,7 +554,8 @@ fn floating_panes_persist_across_toggles() {
|
|||
let new_pane_id = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
// here we send bytes to the pane when it's not visible to make sure they're still handled and
|
||||
// we see them once we toggle the panes back
|
||||
|
|
@ -572,7 +586,8 @@ fn toggle_floating_panes_off() {
|
|||
let new_pane_id = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -600,7 +615,8 @@ fn toggle_floating_panes_on() {
|
|||
let new_pane_id = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -633,11 +649,16 @@ fn five_new_floating_panes() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -672,7 +693,8 @@ fn increase_floating_pane_size() {
|
|||
let new_pane_id_1 = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -700,7 +722,8 @@ fn decrease_floating_pane_size() {
|
|||
let new_pane_id_1 = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -728,7 +751,8 @@ fn resize_floating_pane_left() {
|
|||
let new_pane_id_1 = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -756,7 +780,8 @@ fn resize_floating_pane_right() {
|
|||
let new_pane_id_1 = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -784,7 +809,8 @@ fn resize_floating_pane_up() {
|
|||
let new_pane_id_1 = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -812,7 +838,8 @@ fn resize_floating_pane_down() {
|
|||
let new_pane_id_1 = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -844,11 +871,16 @@ fn move_floating_pane_focus_left() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -894,11 +926,16 @@ fn move_floating_pane_focus_right() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -945,11 +982,16 @@ fn move_floating_pane_focus_up() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -995,11 +1037,16 @@ fn move_floating_pane_focus_down() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -1046,11 +1093,16 @@ fn move_floating_pane_focus_with_mouse() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -1099,11 +1151,16 @@ fn move_pane_focus_with_mouse_to_non_floating_pane() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -1152,11 +1209,16 @@ fn drag_pane_with_mouse() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -1205,11 +1267,16 @@ fn mark_text_inside_floating_pane() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -1266,11 +1333,16 @@ fn resize_tab_with_floating_panes() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -1314,11 +1386,16 @@ fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically() {
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -1359,11 +1436,16 @@ fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically_and_expand_b
|
|||
let new_pane_id_5 = PaneId::Terminal(6);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id_1, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_2, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_3, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_4, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_5, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id_1, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_2, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_3, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_4, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.new_pane(new_pane_id_5, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -1404,7 +1486,8 @@ fn embed_floating_pane() {
|
|||
let new_pane_id = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am scratch terminal".as_bytes()),
|
||||
|
|
@ -1431,7 +1514,8 @@ fn float_embedded_pane() {
|
|||
let mut tab = create_new_tab(size, ModeInfo::default());
|
||||
let new_pane_id = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am an embedded pane".as_bytes()),
|
||||
|
|
@ -1533,7 +1617,8 @@ fn rename_floating_pane() {
|
|||
let mut tab = create_new_tab(size, ModeInfo::default());
|
||||
let new_pane_id = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.handle_pty_bytes(
|
||||
2,
|
||||
Vec::from("\n\n\n I am a floating pane".as_bytes()),
|
||||
|
|
@ -1618,7 +1703,8 @@ fn move_floating_pane_with_sixel_image() {
|
|||
let mut output = Output::new(sixel_image_store.clone(), character_cell_size);
|
||||
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
let fixture = read_fixture("sixel-image-500px.six");
|
||||
tab.handle_pty_bytes(2, fixture).unwrap();
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id)
|
||||
|
|
@ -1655,7 +1741,8 @@ fn floating_pane_above_sixel_image() {
|
|||
let mut output = Output::new(sixel_image_store.clone(), character_cell_size);
|
||||
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
let fixture = read_fixture("sixel-image-500px.six");
|
||||
tab.handle_pty_bytes(1, fixture).unwrap();
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id)
|
||||
|
|
@ -1711,7 +1798,8 @@ fn suppress_floating_pane() {
|
|||
let mut output = Output::default();
|
||||
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
|
||||
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
|
||||
.unwrap();
|
||||
|
|
@ -1764,7 +1852,8 @@ fn close_suppressing_floating_pane() {
|
|||
let mut output = Output::default();
|
||||
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
|
||||
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
|
||||
.unwrap();
|
||||
|
|
@ -1821,7 +1910,8 @@ fn suppress_floating_pane_embed_it_and_close_it() {
|
|||
let mut output = Output::default();
|
||||
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
|
||||
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
|
||||
.unwrap();
|
||||
|
|
@ -1879,7 +1969,8 @@ fn resize_whole_tab_while_floting_pane_is_suppressed() {
|
|||
let mut output = Output::default();
|
||||
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
tab.suppress_active_pane(editor_pane_id, client_id).unwrap();
|
||||
tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
|
||||
.unwrap();
|
||||
|
|
@ -1978,7 +2069,8 @@ fn enter_search_floating_pane() {
|
|||
let new_pane_id = PaneId::Terminal(2);
|
||||
let mut output = Output::default();
|
||||
tab.toggle_floating_panes(client_id, None).unwrap();
|
||||
tab.new_pane(new_pane_id, Some(client_id)).unwrap();
|
||||
tab.new_pane(new_pane_id, None, None, Some(client_id))
|
||||
.unwrap();
|
||||
|
||||
let pane_content = read_fixture("grid_copy");
|
||||
tab.handle_pty_bytes(2, pane_content).unwrap();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -31,6 +31,7 @@ impl From<std::io::Result<usize>> for ReadResult {
|
|||
|
||||
pub(crate) struct TerminalBytes {
|
||||
pid: RawFd,
|
||||
terminal_id: u32,
|
||||
senders: ThreadSenders,
|
||||
async_reader: Box<dyn AsyncReader>,
|
||||
debug: bool,
|
||||
|
|
@ -47,9 +48,11 @@ impl TerminalBytes {
|
|||
senders: ThreadSenders,
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
debug: bool,
|
||||
terminal_id: u32,
|
||||
) -> Self {
|
||||
TerminalBytes {
|
||||
pid,
|
||||
terminal_id,
|
||||
senders,
|
||||
debug,
|
||||
async_reader: os_input.async_file_reader(pid),
|
||||
|
|
@ -77,7 +80,6 @@ impl TerminalBytes {
|
|||
let mut buf = [0u8; 65536];
|
||||
loop {
|
||||
match self.deadline_read(&mut buf).await {
|
||||
// match deadline_read(async_reader.as_mut(), self.render_deadline, &mut buf).await {
|
||||
ReadResult::Ok(0) | ReadResult::Err(_) => break, // EOF or error
|
||||
ReadResult::Timeout => {
|
||||
let time_to_send_render =
|
||||
|
|
@ -93,7 +95,7 @@ impl TerminalBytes {
|
|||
let _ = debug_to_file(bytes, self.pid);
|
||||
}
|
||||
self.async_send_to_screen(ScreenInstruction::PtyBytes(
|
||||
self.pid,
|
||||
self.terminal_id,
|
||||
bytes.to_vec(),
|
||||
))
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,12 @@ fn background_color(characters: &str, color: Option<PaletteColor>) -> Vec<Termin
|
|||
colored_string
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ExitStatus {
|
||||
Code(i32),
|
||||
Exited,
|
||||
}
|
||||
|
||||
pub struct FrameParams {
|
||||
pub focused_client: Option<ClientId>,
|
||||
pub is_main_client: bool,
|
||||
|
|
@ -81,6 +87,7 @@ pub struct PaneFrame {
|
|||
pub is_main_client: bool,
|
||||
pub other_cursors_exist_in_session: bool,
|
||||
pub other_focused_clients: Vec<ClientId>,
|
||||
exit_status: Option<ExitStatus>,
|
||||
}
|
||||
|
||||
impl PaneFrame {
|
||||
|
|
@ -100,8 +107,15 @@ impl PaneFrame {
|
|||
is_main_client: frame_params.is_main_client,
|
||||
other_focused_clients: frame_params.other_focused_clients,
|
||||
other_cursors_exist_in_session: frame_params.other_cursors_exist_in_session,
|
||||
exit_status: None,
|
||||
}
|
||||
}
|
||||
pub fn add_exit_status(&mut self, exit_status: Option<i32>) {
|
||||
self.exit_status = match exit_status {
|
||||
Some(exit_status) => Some(ExitStatus::Code(exit_status)),
|
||||
None => Some(ExitStatus::Exited),
|
||||
};
|
||||
}
|
||||
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))
|
||||
|
|
@ -594,6 +608,65 @@ impl PaneFrame {
|
|||
.or_else(|| Some(self.title_line_without_middle()))
|
||||
.unwrap()
|
||||
}
|
||||
fn render_held_undertitle(&self) -> 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.unwrap(); // 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 left_boundary =
|
||||
foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color);
|
||||
let mut right_boundary =
|
||||
foreground_color(self.get_corner(boundary_type::BOTTOM_RIGHT), self.color);
|
||||
if self.is_main_client {
|
||||
let (mut second_part, second_part_len) = self.second_held_title_part_full();
|
||||
let full_text_len = first_part_len + second_part_len;
|
||||
if full_text_len <= max_undertitle_length {
|
||||
// render exit status and tips
|
||||
let mut padding = String::new();
|
||||
for _ in full_text_len..max_undertitle_length {
|
||||
padding.push_str(boundary_type::HORIZONTAL);
|
||||
}
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_boundary);
|
||||
ret.append(&mut first_part);
|
||||
ret.append(&mut second_part);
|
||||
ret.append(&mut foreground_color(&padding, self.color));
|
||||
ret.append(&mut right_boundary);
|
||||
ret
|
||||
} else if first_part_len <= max_undertitle_length {
|
||||
// render only exit status
|
||||
let mut padding = String::new();
|
||||
for _ in first_part_len..max_undertitle_length {
|
||||
padding.push_str(boundary_type::HORIZONTAL);
|
||||
}
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_boundary);
|
||||
ret.append(&mut first_part);
|
||||
ret.append(&mut foreground_color(&padding, self.color));
|
||||
ret.append(&mut right_boundary);
|
||||
ret
|
||||
} else {
|
||||
self.empty_undertitle(max_undertitle_length)
|
||||
}
|
||||
} else {
|
||||
if first_part_len <= max_undertitle_length {
|
||||
// render first part
|
||||
let full_text_len = first_part_len;
|
||||
let mut padding = String::new();
|
||||
for _ in full_text_len..max_undertitle_length {
|
||||
padding.push_str(boundary_type::HORIZONTAL);
|
||||
}
|
||||
let mut ret = vec![];
|
||||
ret.append(&mut left_boundary);
|
||||
ret.append(&mut first_part);
|
||||
ret.append(&mut foreground_color(&padding, self.color));
|
||||
ret.append(&mut right_boundary);
|
||||
ret
|
||||
} else {
|
||||
self.empty_undertitle(max_undertitle_length)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn render(&self) -> (Vec<CharacterChunk>, Option<String>) {
|
||||
let mut character_chunks = vec![];
|
||||
for row in 0..self.geom.rows {
|
||||
|
|
@ -605,24 +678,30 @@ impl PaneFrame {
|
|||
character_chunks.push(CharacterChunk::new(title, x, y));
|
||||
} else if row == self.geom.rows - 1 {
|
||||
// bottom row
|
||||
let mut bottom_row = vec![];
|
||||
for col in 0..self.geom.cols {
|
||||
let boundary = if col == 0 {
|
||||
// bottom left corner
|
||||
self.get_corner(boundary_type::BOTTOM_LEFT)
|
||||
} else if col == self.geom.cols - 1 {
|
||||
// bottom right corner
|
||||
self.get_corner(boundary_type::BOTTOM_RIGHT)
|
||||
} else {
|
||||
boundary_type::HORIZONTAL
|
||||
};
|
||||
if self.exit_status.is_some() {
|
||||
let x = self.geom.x;
|
||||
let y = self.geom.y + row;
|
||||
character_chunks.push(CharacterChunk::new(self.render_held_undertitle(), x, y));
|
||||
} else {
|
||||
let mut bottom_row = vec![];
|
||||
for col in 0..self.geom.cols {
|
||||
let boundary = if col == 0 {
|
||||
// bottom left corner
|
||||
self.get_corner(boundary_type::BOTTOM_LEFT)
|
||||
} else if col == self.geom.cols - 1 {
|
||||
// bottom right corner
|
||||
self.get_corner(boundary_type::BOTTOM_RIGHT)
|
||||
} else {
|
||||
boundary_type::HORIZONTAL
|
||||
};
|
||||
|
||||
let mut boundary_character = foreground_color(boundary, self.color);
|
||||
bottom_row.append(&mut boundary_character);
|
||||
let mut boundary_character = foreground_color(boundary, self.color);
|
||||
bottom_row.append(&mut boundary_character);
|
||||
}
|
||||
let x = self.geom.x;
|
||||
let y = self.geom.y + row;
|
||||
character_chunks.push(CharacterChunk::new(bottom_row, x, y));
|
||||
}
|
||||
let x = self.geom.x;
|
||||
let y = self.geom.y + row;
|
||||
character_chunks.push(CharacterChunk::new(bottom_row, x, y));
|
||||
} else {
|
||||
let boundary_character_left = foreground_color(boundary_type::VERTICAL, self.color);
|
||||
let boundary_character_right =
|
||||
|
|
@ -639,4 +718,106 @@ impl PaneFrame {
|
|||
}
|
||||
(character_chunks, None)
|
||||
}
|
||||
fn first_held_title_part_full(
|
||||
&self,
|
||||
exit_status: ExitStatus,
|
||||
) -> (Vec<TerminalCharacter>, usize) {
|
||||
// (title part, length)
|
||||
match exit_status {
|
||||
ExitStatus::Code(exit_code) => {
|
||||
let mut first_part = vec![];
|
||||
let left_bracket = " [ ";
|
||||
let exited_text = "EXIT CODE: ";
|
||||
let exit_code_text = format!("{}", exit_code);
|
||||
let exit_code_color = if exit_code == 0 {
|
||||
self.style.colors.green
|
||||
} else {
|
||||
self.style.colors.red
|
||||
};
|
||||
let right_bracket = " ] ";
|
||||
first_part.append(&mut foreground_color(left_bracket, self.color));
|
||||
first_part.append(&mut foreground_color(exited_text, self.color));
|
||||
first_part.append(&mut foreground_color(
|
||||
&exit_code_text,
|
||||
Some(exit_code_color),
|
||||
));
|
||||
first_part.append(&mut foreground_color(right_bracket, self.color));
|
||||
(
|
||||
first_part,
|
||||
left_bracket.len()
|
||||
+ exited_text.len()
|
||||
+ exit_code_text.len()
|
||||
+ right_bracket.len(),
|
||||
)
|
||||
},
|
||||
ExitStatus::Exited => {
|
||||
let mut first_part = vec![];
|
||||
let left_bracket = " [ ";
|
||||
let exited_text = "EXITED";
|
||||
let right_bracket = " ] ";
|
||||
first_part.append(&mut foreground_color(left_bracket, self.color));
|
||||
first_part.append(&mut foreground_color(
|
||||
exited_text,
|
||||
Some(self.style.colors.red),
|
||||
));
|
||||
first_part.append(&mut foreground_color(right_bracket, self.color));
|
||||
(
|
||||
first_part,
|
||||
left_bracket.len() + exited_text.len() + right_bracket.len(),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
fn second_held_title_part_full(&self) -> (Vec<TerminalCharacter>, usize) {
|
||||
// (title part, length)
|
||||
let mut second_part = vec![];
|
||||
let left_enter_bracket = "<";
|
||||
let enter_text = "ENTER";
|
||||
let right_enter_bracket = ">";
|
||||
let enter_tip = " to re-run, ";
|
||||
let left_break_bracket = "<";
|
||||
let break_text = "Ctrl-c";
|
||||
let right_break_bracket = ">";
|
||||
let break_tip = " to exit ";
|
||||
second_part.append(&mut foreground_color(left_enter_bracket, self.color));
|
||||
second_part.append(&mut foreground_color(
|
||||
enter_text,
|
||||
Some(self.style.colors.orange),
|
||||
));
|
||||
second_part.append(&mut foreground_color(right_enter_bracket, self.color));
|
||||
second_part.append(&mut foreground_color(enter_tip, self.color));
|
||||
second_part.append(&mut foreground_color(left_break_bracket, self.color));
|
||||
second_part.append(&mut foreground_color(
|
||||
break_text,
|
||||
Some(self.style.colors.orange),
|
||||
));
|
||||
second_part.append(&mut foreground_color(right_break_bracket, self.color));
|
||||
second_part.append(&mut foreground_color(break_tip, self.color));
|
||||
(
|
||||
second_part,
|
||||
left_enter_bracket.len()
|
||||
+ enter_text.len()
|
||||
+ right_enter_bracket.len()
|
||||
+ enter_tip.len()
|
||||
+ left_break_bracket.len()
|
||||
+ break_text.len()
|
||||
+ right_break_bracket.len()
|
||||
+ break_tip.len(),
|
||||
)
|
||||
}
|
||||
fn empty_undertitle(&self, max_undertitle_length: usize) -> Vec<TerminalCharacter> {
|
||||
let mut left_boundary =
|
||||
foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color);
|
||||
let mut right_boundary =
|
||||
foreground_color(self.get_corner(boundary_type::BOTTOM_RIGHT), self.color);
|
||||
let mut ret = vec![];
|
||||
let mut padding = String::new();
|
||||
for _ in 0..max_undertitle_length {
|
||||
padding.push_str(boundary_type::HORIZONTAL);
|
||||
}
|
||||
ret.append(&mut left_boundary);
|
||||
ret.append(&mut foreground_color(&padding, self.color));
|
||||
ret.append(&mut right_boundary);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ fn get_cwd() {
|
|||
let server = ServerOsInputOutput {
|
||||
orig_termios: Arc::new(Mutex::new(test_termios)),
|
||||
client_senders: Arc::default(),
|
||||
terminal_id_to_raw_fd: Arc::default(),
|
||||
};
|
||||
|
||||
let pid = nix::unistd::getpid();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use super::{screen_thread_main, CopyOptions, Screen, ScreenInstruction};
|
|||
use crate::panes::PaneId;
|
||||
use crate::{
|
||||
channels::SenderWithContext,
|
||||
os_input_output::{AsyncReader, Pid, ServerOsApi},
|
||||
os_input_output::{AsyncReader, Pid, ServerOsApi, SpawnTerminalError},
|
||||
route::route_action,
|
||||
thread_bus::Bus,
|
||||
ClientId, ServerInstruction, SessionMetaData, ThreadSenders,
|
||||
|
|
@ -12,7 +12,7 @@ use std::path::PathBuf;
|
|||
use zellij_utils::cli::CliAction;
|
||||
use zellij_utils::errors::ErrorContext;
|
||||
use zellij_utils::input::actions::{Action, Direction, ResizeDirection};
|
||||
use zellij_utils::input::command::TerminalAction;
|
||||
use zellij_utils::input::command::{RunCommand, TerminalAction};
|
||||
use zellij_utils::input::layout::{PaneLayout, SplitDirection};
|
||||
use zellij_utils::input::options::Options;
|
||||
use zellij_utils::ipc::IpcReceiverWithContext;
|
||||
|
|
@ -126,15 +126,15 @@ struct FakeInputOutput {
|
|||
}
|
||||
|
||||
impl ServerOsApi for FakeInputOutput {
|
||||
fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) {
|
||||
fn set_terminal_size_using_terminal_id(&self, _terminal_id: u32, _cols: u16, _rows: u16) {
|
||||
// noop
|
||||
}
|
||||
fn spawn_terminal(
|
||||
&self,
|
||||
_file_to_open: TerminalAction,
|
||||
_quit_db: Box<dyn Fn(PaneId) + Send>,
|
||||
_quit_db: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>,
|
||||
_default_editor: Option<PathBuf>,
|
||||
) -> Result<(RawFd, RawFd), &'static str> {
|
||||
) -> Result<(u32, RawFd, RawFd), SpawnTerminalError> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
|
|
@ -143,10 +143,10 @@ impl ServerOsApi for FakeInputOutput {
|
|||
fn async_file_reader(&self, _fd: RawFd) -> Box<dyn AsyncReader> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result<usize, nix::Error> {
|
||||
fn write_to_tty_stdin(&self, _id: u32, _buf: &[u8]) -> Result<usize, nix::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn tcdrain(&self, _fd: RawFd) -> Result<(), nix::Error> {
|
||||
fn tcdrain(&self, _id: u32) -> Result<(), nix::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn kill(&self, _pid: Pid) -> Result<(), nix::Error> {
|
||||
|
|
@ -195,6 +195,17 @@ impl ServerOsApi for FakeInputOutput {
|
|||
.insert(filename, contents);
|
||||
}
|
||||
}
|
||||
fn re_run_command_in_terminal(
|
||||
&self,
|
||||
_terminal_id: u32,
|
||||
_run_command: RunCommand,
|
||||
_quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status
|
||||
) -> Result<(RawFd, RawFd), SpawnTerminalError> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn clear_terminal_id(&self, _terminal_id: u32) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn create_new_screen(size: Size) -> Screen {
|
||||
|
|
@ -272,7 +283,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 i32);
|
||||
pane_ids.push(i as u32);
|
||||
}
|
||||
let _ = self.to_screen.send(ScreenInstruction::NewTab(
|
||||
pane_layout,
|
||||
|
|
@ -285,7 +296,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 i32);
|
||||
pane_ids.push(i as u32);
|
||||
}
|
||||
let _ = self.to_screen.send(ScreenInstruction::NewTab(
|
||||
tab_layout,
|
||||
|
|
@ -412,7 +423,7 @@ macro_rules! log_actions_in_thread {
|
|||
};
|
||||
}
|
||||
|
||||
fn new_tab(screen: &mut Screen, pid: i32) {
|
||||
fn new_tab(screen: &mut Screen, pid: u32) {
|
||||
let client_id = 1;
|
||||
screen
|
||||
.new_tab(PaneLayout::default(), vec![pid], client_id)
|
||||
|
|
@ -746,7 +757,9 @@ fn switch_to_tab_with_fullscreen() {
|
|||
new_tab(&mut screen, 1);
|
||||
{
|
||||
let active_tab = screen.get_active_tab_mut(1).unwrap();
|
||||
active_tab.new_pane(PaneId::Terminal(2), Some(1)).unwrap();
|
||||
active_tab
|
||||
.new_pane(PaneId::Terminal(2), None, None, Some(1))
|
||||
.unwrap();
|
||||
active_tab.toggle_active_pane_fullscreen(1);
|
||||
}
|
||||
new_tab(&mut screen, 2);
|
||||
|
|
@ -859,7 +872,9 @@ fn attach_after_first_tab_closed() {
|
|||
new_tab(&mut screen, 1);
|
||||
{
|
||||
let active_tab = screen.get_active_tab_mut(1).unwrap();
|
||||
active_tab.new_pane(PaneId::Terminal(2), Some(1)).unwrap();
|
||||
active_tab
|
||||
.new_pane(PaneId::Terminal(2), None, None, Some(1))
|
||||
.unwrap();
|
||||
active_tab.toggle_active_pane_fullscreen(1);
|
||||
}
|
||||
new_tab(&mut screen, 2);
|
||||
|
|
@ -1802,7 +1817,7 @@ pub fn send_cli_new_pane_action_with_default_parameters() {
|
|||
direction: None,
|
||||
command: None,
|
||||
cwd: None,
|
||||
floating: None,
|
||||
floating: false,
|
||||
};
|
||||
send_cli_action_to_server(
|
||||
&session_metadata,
|
||||
|
|
@ -1839,7 +1854,7 @@ pub fn send_cli_new_pane_action_with_split_direction() {
|
|||
direction: Some(Direction::Right),
|
||||
command: None,
|
||||
cwd: None,
|
||||
floating: None,
|
||||
floating: false,
|
||||
};
|
||||
send_cli_action_to_server(
|
||||
&session_metadata,
|
||||
|
|
@ -1876,7 +1891,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() {
|
|||
direction: Some(Direction::Right),
|
||||
command: Some("htop".into()),
|
||||
cwd: Some("/some/folder".into()),
|
||||
floating: None,
|
||||
floating: false,
|
||||
};
|
||||
send_cli_action_to_server(
|
||||
&session_metadata,
|
||||
|
|
@ -1913,7 +1928,7 @@ pub fn send_cli_edit_action_with_default_parameters() {
|
|||
file: PathBuf::from("/file/to/edit"),
|
||||
direction: None,
|
||||
line_number: None,
|
||||
floating: None,
|
||||
floating: false,
|
||||
};
|
||||
send_cli_action_to_server(
|
||||
&session_metadata,
|
||||
|
|
@ -1950,7 +1965,7 @@ pub fn send_cli_edit_action_with_line_number() {
|
|||
file: PathBuf::from("/file/to/edit"),
|
||||
direction: None,
|
||||
line_number: Some(100),
|
||||
floating: None,
|
||||
floating: false,
|
||||
};
|
||||
send_cli_action_to_server(
|
||||
&session_metadata,
|
||||
|
|
@ -1987,7 +2002,7 @@ pub fn send_cli_edit_action_with_split_direction() {
|
|||
file: PathBuf::from("/file/to/edit"),
|
||||
direction: Some(Direction::Down),
|
||||
line_number: None,
|
||||
floating: None,
|
||||
floating: false,
|
||||
};
|
||||
send_cli_action_to_server(
|
||||
&session_metadata,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source: zellij-server/src/./unit/screen_tests.rs
|
||||
assertion_line: 1618
|
||||
assertion_line: 1937
|
||||
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
|
||||
---
|
||||
[SpawnTerminal(Some(OpenFile("/file/to/edit", None)), ClientId(10)), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit]
|
||||
[SpawnTerminal(Some(OpenFile("/file/to/edit", None)), Some(false), 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: 1650
|
||||
assertion_line: 1974
|
||||
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
|
||||
---
|
||||
[SpawnTerminal(Some(OpenFile("/file/to/edit", Some(100))), ClientId(10)), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit]
|
||||
[SpawnTerminal(Some(OpenFile("/file/to/edit", Some(100))), Some(false), 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: 1682
|
||||
assertion_line: 2011
|
||||
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
|
||||
---
|
||||
[SpawnTerminalHorizontally(Some(OpenFile("/file/to/edit", None)), 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: 1889
|
||||
assertion_line: 1900
|
||||
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
|
||||
---
|
||||
[SpawnTerminalVertically(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder") })), 10), UpdateActivePane(Some(Terminal(0)), 1), 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 })), 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: 1566
|
||||
assertion_line: 1826
|
||||
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
|
||||
---
|
||||
[SpawnTerminal(None, ClientId(10)), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit]
|
||||
[SpawnTerminal(None, Some(false), 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: 1599
|
||||
assertion_line: 1863
|
||||
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
|
||||
---
|
||||
[SpawnTerminalVertically(None, 10), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit]
|
||||
[SpawnTerminalVertically(None, 10), UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), Exit]
|
||||
|
|
|
|||
|
|
@ -394,6 +394,7 @@ fn host_open_file(plugin_env: &PluginEnv) {
|
|||
.senders
|
||||
.send_to_pty(PtyInstruction::SpawnTerminal(
|
||||
Some(TerminalAction::OpenFile(path, None)),
|
||||
None,
|
||||
ClientOrTabIndex::TabIndex(plugin_env.tab_index),
|
||||
))
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -124,18 +124,18 @@ pub enum Sessions {
|
|||
#[clap(visible_alias = "ac")]
|
||||
#[clap(subcommand)]
|
||||
Action(CliAction),
|
||||
/// Send actions to a specific session
|
||||
#[clap(visible_alias = "c")]
|
||||
Command {
|
||||
/// Run a command in a new pane
|
||||
#[clap(visible_alias = "r")]
|
||||
Run {
|
||||
command: Option<String>,
|
||||
#[clap(short, long, value_parser, conflicts_with("floating"))]
|
||||
direction: Option<Direction>,
|
||||
#[clap(long, value_parser)]
|
||||
cwd: Option<PathBuf>,
|
||||
#[clap(short, long, value_parser, default_missing_value("true"))]
|
||||
floating: Option<bool>,
|
||||
#[clap(short, long, value_parser, default_value("false"), takes_value(false))]
|
||||
floating: bool,
|
||||
},
|
||||
/// Edit file with default $EDITOR / $VISUAL in a specific session
|
||||
/// Edit file with default $EDITOR / $VISUAL
|
||||
#[clap(visible_alias = "e")]
|
||||
Edit {
|
||||
file: PathBuf,
|
||||
|
|
@ -143,8 +143,8 @@ pub enum Sessions {
|
|||
line_number: Option<usize>,
|
||||
#[clap(short, long, value_parser, conflicts_with("floating"))]
|
||||
direction: Option<Direction>,
|
||||
#[clap(short, long, value_parser, default_missing_value("true"))]
|
||||
floating: Option<bool>,
|
||||
#[clap(short, long, value_parser, default_value("false"), takes_value(false))]
|
||||
floating: bool,
|
||||
},
|
||||
ConvertConfig {
|
||||
old_config_file: PathBuf,
|
||||
|
|
@ -201,7 +201,7 @@ pub enum CliAction {
|
|||
TogglePaneFrames,
|
||||
/// Toggle between sending text commands to all panes on the current tab and normal mode.
|
||||
ToggleActiveSyncTab,
|
||||
/// Open a new pane in the specified direction [right|left|up|down]
|
||||
/// Open a new pane in the specified direction [right|down]
|
||||
/// If no direction is specified, will try to use the biggest available space.
|
||||
NewPane {
|
||||
#[clap(short, long, value_parser, conflicts_with("floating"))]
|
||||
|
|
@ -210,8 +210,8 @@ pub enum CliAction {
|
|||
command: Option<String>,
|
||||
#[clap(long, value_parser)]
|
||||
cwd: Option<PathBuf>,
|
||||
#[clap(short, long, value_parser, default_missing_value("true"))]
|
||||
floating: Option<bool>,
|
||||
#[clap(short, long, value_parser, default_value("false"), takes_value(false))]
|
||||
floating: bool,
|
||||
},
|
||||
/// Open the specified file in a new zellij pane with your default EDITOR
|
||||
Edit {
|
||||
|
|
@ -220,8 +220,8 @@ pub enum CliAction {
|
|||
direction: Option<Direction>,
|
||||
#[clap(short, long, value_parser)]
|
||||
line_number: Option<usize>,
|
||||
#[clap(short, long, value_parser, default_missing_value("true"))]
|
||||
floating: Option<bool>,
|
||||
#[clap(short, long, value_parser, default_value("false"), takes_value(false))]
|
||||
floating: bool,
|
||||
},
|
||||
/// Switch input mode of all connected clients [locked|pane|tab|resize|move|search|session]
|
||||
SwitchMode { input_mode: InputMode },
|
||||
|
|
|
|||
|
|
@ -273,6 +273,7 @@ pub enum ScreenContext {
|
|||
SetFixedHeight,
|
||||
SetFixedWidth,
|
||||
ClosePane,
|
||||
HoldPane,
|
||||
UpdatePaneName,
|
||||
UndoRenamePane,
|
||||
NewTab,
|
||||
|
|
@ -326,6 +327,7 @@ pub enum PtyContext {
|
|||
NewTab,
|
||||
ClosePane,
|
||||
CloseTab,
|
||||
ReRunCommandInPane,
|
||||
Exit,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ pub enum Action {
|
|||
/// If no direction is specified, will try to use the biggest available space.
|
||||
NewPane(Option<Direction>),
|
||||
/// Open the file in a new pane using the default editor
|
||||
EditFile(PathBuf, Option<usize>, Option<Direction>, Option<bool>), // usize is an optional line number, bool is floating true/false
|
||||
EditFile(PathBuf, Option<usize>, Option<Direction>, bool), // usize is an optional line number, bool is floating true/false
|
||||
/// Open a new floating pane
|
||||
NewFloatingPane(Option<RunCommandAction>),
|
||||
/// Open a new tiled (embedded, non-floating) pane
|
||||
|
|
@ -288,23 +288,29 @@ impl Action {
|
|||
} => match command {
|
||||
Some(command) => {
|
||||
let (command, args) = split_command_and_args(command);
|
||||
let cwd = cwd.or_else(|| std::env::current_dir().ok());
|
||||
let run_command_action = RunCommandAction {
|
||||
command,
|
||||
args,
|
||||
cwd,
|
||||
direction,
|
||||
hold_on_close: true,
|
||||
};
|
||||
match floating {
|
||||
Some(true) => Ok(vec![Action::NewFloatingPane(Some(run_command_action))]),
|
||||
_ => Ok(vec![Action::NewTiledPane(
|
||||
if floating {
|
||||
Ok(vec![Action::NewFloatingPane(Some(run_command_action))])
|
||||
} else {
|
||||
Ok(vec![Action::NewTiledPane(
|
||||
direction,
|
||||
Some(run_command_action),
|
||||
)]),
|
||||
)])
|
||||
}
|
||||
},
|
||||
None => match floating {
|
||||
Some(true) => Ok(vec![Action::NewFloatingPane(None)]),
|
||||
_ => Ok(vec![Action::NewTiledPane(direction, None)]),
|
||||
None => {
|
||||
if floating {
|
||||
Ok(vec![Action::NewFloatingPane(None)])
|
||||
} else {
|
||||
Ok(vec![Action::NewTiledPane(direction, None)])
|
||||
}
|
||||
},
|
||||
},
|
||||
CliAction::Edit {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,24 @@ pub struct RunCommand {
|
|||
pub args: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub cwd: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub hold_on_close: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RunCommand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut command: String = self
|
||||
.command
|
||||
.as_path()
|
||||
.as_os_str()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
for arg in &self.args {
|
||||
command.push(' ');
|
||||
command.push_str(arg);
|
||||
}
|
||||
write!(f, "{}", command)
|
||||
}
|
||||
}
|
||||
|
||||
/// Intermediate representation
|
||||
|
|
@ -30,6 +48,8 @@ pub struct RunCommandAction {
|
|||
pub cwd: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub direction: Option<Direction>,
|
||||
#[serde(default)]
|
||||
pub hold_on_close: bool,
|
||||
}
|
||||
|
||||
impl From<RunCommandAction> for RunCommand {
|
||||
|
|
@ -38,6 +58,7 @@ impl From<RunCommandAction> for RunCommand {
|
|||
command: action.command,
|
||||
args: action.args,
|
||||
cwd: action.cwd,
|
||||
hold_on_close: action.hold_on_close,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ fn layout_with_command_panes() {
|
|||
children: vec![PaneLayout {
|
||||
run: Some(Run::Command(RunCommand {
|
||||
command: PathBuf::from("htop"),
|
||||
hold_on_close: true,
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
|
|
@ -226,6 +227,7 @@ fn layout_with_command_panes_and_cwd() {
|
|||
run: Some(Run::Command(RunCommand {
|
||||
command: PathBuf::from("htop"),
|
||||
cwd: Some(PathBuf::from("/path/to/my/cwd")),
|
||||
hold_on_close: true,
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
|
|
@ -254,6 +256,7 @@ fn layout_with_command_panes_and_cwd_and_args() {
|
|||
command: PathBuf::from("htop"),
|
||||
cwd: Some(PathBuf::from("/path/to/my/cwd")),
|
||||
args: vec![String::from("-h"), String::from("-v")],
|
||||
hold_on_close: true,
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source: zellij-utils/src/input/./unit/layout_test.rs
|
||||
assertion_line: 648
|
||||
assertion_line: 570
|
||||
expression: "format!(\"{:#?}\", layout)"
|
||||
---
|
||||
Layout {
|
||||
|
|
@ -40,6 +40,7 @@ Layout {
|
|||
command: "htop",
|
||||
args: [],
|
||||
cwd: None,
|
||||
hold_on_close: true,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ impl<'a> KdlLayoutParser<'a> {
|
|||
command,
|
||||
args: args.unwrap_or_else(|| vec![]),
|
||||
cwd,
|
||||
hold_on_close: true,
|
||||
}))),
|
||||
_ => Ok(None),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -590,12 +590,10 @@ impl TryFrom<(&str, &KdlDocument)> for PaletteColor {
|
|||
}
|
||||
|
||||
impl TryFrom<&KdlNode> for Action {
|
||||
// type Error = Box<dyn std::error::Error>;
|
||||
type Error = ConfigError;
|
||||
fn try_from(kdl_action: &KdlNode) -> Result<Self, Self::Error> {
|
||||
let action_name = kdl_name!(kdl_action);
|
||||
let action_arguments: Vec<&KdlEntry> = kdl_argument_values!(kdl_action);
|
||||
// let action_arguments: Vec<&KdlValue> = kdl_argument_values!(kdl_action);
|
||||
let action_children: Vec<&KdlDocument> = kdl_children!(kdl_action);
|
||||
match action_name {
|
||||
"Quit" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action),
|
||||
|
|
@ -744,6 +742,7 @@ impl TryFrom<&KdlNode> for Action {
|
|||
args,
|
||||
cwd,
|
||||
direction,
|
||||
hold_on_close: true,
|
||||
};
|
||||
Ok(Action::Run(run_command_action))
|
||||
},
|
||||
|
|
@ -1473,7 +1472,12 @@ impl RunCommand {
|
|||
.collect(),
|
||||
None => vec![],
|
||||
};
|
||||
Ok(RunCommand { command, args, cwd })
|
||||
Ok(RunCommand {
|
||||
command,
|
||||
args,
|
||||
cwd,
|
||||
hold_on_close: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ pub mod colors {
|
|||
pub const GREEN: u8 = 154;
|
||||
pub const GRAY: u8 = 238;
|
||||
pub const BRIGHT_GRAY: u8 = 245;
|
||||
pub const RED: u8 = 88;
|
||||
pub const RED: u8 = 124;
|
||||
pub const ORANGE: u8 = 166;
|
||||
pub const BLACK: u8 = 16;
|
||||
pub const MAGENTA: u8 = 201;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue