Gate manual TUI launch input
ci / test (push) Has been cancelled
ci / fuzz-smoke (push) Has been cancelled
ci / windows-client (push) Has been cancelled
ci / package-release (linux-x86_64, ubuntu-latest) (push) Has been cancelled
ci / package-release (macos-aarch64, macos-14) (push) Has been cancelled
ci / package-release (macos-x86_64, macos-13) (push) Has been cancelled
ci / package-release (windows-x86_64, windows-latest) (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
ci / test (push) Has been cancelled
ci / fuzz-smoke (push) Has been cancelled
ci / windows-client (push) Has been cancelled
ci / package-release (linux-x86_64, ubuntu-latest) (push) Has been cancelled
ci / package-release (macos-aarch64, macos-14) (push) Has been cancelled
ci / package-release (macos-x86_64, macos-13) (push) Has been cancelled
ci / package-release (windows-x86_64, windows-latest) (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
This commit is contained in:
+132
-2
@@ -2844,6 +2844,7 @@ async fn run_terminal(
|
||||
let mut pending_user_input: VecDeque<Vec<u8>> = VecDeque::new();
|
||||
let mut pending_user_input_bytes = 0usize;
|
||||
let mut startup_input_hold_until: Option<Instant> = None;
|
||||
let mut shell_command_line: Vec<u8> = Vec::new();
|
||||
if let Some(frame) = first_frame {
|
||||
if !forward_only {
|
||||
render_frame(&frame)?;
|
||||
@@ -2920,6 +2921,28 @@ async fn run_terminal(
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let hold_after_submit = update_shell_command_line(
|
||||
&mut shell_command_line,
|
||||
&bytes,
|
||||
predictor.alternate_screen,
|
||||
);
|
||||
if hold_after_submit
|
||||
&& let Some((send_now, hold_for_later)) =
|
||||
split_after_command_submit(&bytes)
|
||||
{
|
||||
predictor.observe_input(&send_now)?;
|
||||
send_input(&socket, addr, &cred, &mut send_seq, send_now).await?;
|
||||
startup_input_hold_until =
|
||||
Some(Instant::now() + STARTUP_INPUT_HOLD);
|
||||
if !hold_for_later.is_empty() {
|
||||
queue_pending_user_input(
|
||||
&mut pending_user_input,
|
||||
&mut pending_user_input_bytes,
|
||||
hold_for_later,
|
||||
)?;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if last_packet_at.elapsed() >= Duration::from_secs(2) {
|
||||
queue_pending_user_input(
|
||||
&mut pending_user_input,
|
||||
@@ -3790,6 +3813,81 @@ fn queue_pending_user_input(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn split_after_command_submit(bytes: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> {
|
||||
let split_at = bytes
|
||||
.iter()
|
||||
.position(|byte| matches!(byte, b'\r' | b'\n'))?
|
||||
+ 1;
|
||||
if split_at == bytes.len() {
|
||||
return Some((bytes.to_vec(), Vec::new()));
|
||||
}
|
||||
let later = bytes[split_at..].to_vec();
|
||||
let now = bytes[..split_at].to_vec();
|
||||
Some((now, later))
|
||||
}
|
||||
|
||||
fn update_shell_command_line(line: &mut Vec<u8>, bytes: &[u8], alternate_screen: bool) -> bool {
|
||||
if alternate_screen {
|
||||
line.clear();
|
||||
return false;
|
||||
}
|
||||
let mut submitted_tui = false;
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
match bytes[i] {
|
||||
b'\r' | b'\n' => {
|
||||
submitted_tui |= shell_line_starts_tui(line);
|
||||
line.clear();
|
||||
i += 1;
|
||||
}
|
||||
0x08 | 0x7f => {
|
||||
line.pop();
|
||||
i += 1;
|
||||
}
|
||||
0x20..=0x7e => {
|
||||
line.push(bytes[i]);
|
||||
i += 1;
|
||||
}
|
||||
0x1b => {
|
||||
// Cursor editing makes the cheap line model unreliable; drop it
|
||||
// rather than guessing. The command will still run normally.
|
||||
line.clear();
|
||||
i = bytes.len();
|
||||
}
|
||||
_ => {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
submitted_tui
|
||||
}
|
||||
|
||||
fn shell_line_starts_tui(line: &[u8]) -> bool {
|
||||
let Ok(text) = std::str::from_utf8(line) else {
|
||||
return false;
|
||||
};
|
||||
let command = text
|
||||
.trim_start()
|
||||
.split([' ', '\t', ';', '&', '|'])
|
||||
.next()
|
||||
.unwrap_or("");
|
||||
matches!(
|
||||
command,
|
||||
"tm" | "tmux"
|
||||
| "vim"
|
||||
| "nvim"
|
||||
| "vi"
|
||||
| "less"
|
||||
| "more"
|
||||
| "man"
|
||||
| "top"
|
||||
| "htop"
|
||||
| "btop"
|
||||
| "btm"
|
||||
| "watch"
|
||||
)
|
||||
}
|
||||
|
||||
async fn flush_pending_user_input(
|
||||
socket: &UdpSocket,
|
||||
addr: SocketAddr,
|
||||
@@ -5258,8 +5356,9 @@ mod tests {
|
||||
queue_pending_user_input, raw_contains_host_table, recv_response_until,
|
||||
render_status_clear, render_status_overlay, requested_env, resolved_startup_command,
|
||||
rewrite_forward_command, selected_predict_mode, selected_udp_host, server_version_mismatch,
|
||||
ssh_destination_host, ssh_username, ssh_with_user, startup_command, status_ssh_target,
|
||||
toml_bare_key_or_quoted, update_check_requested, valid_forward_host,
|
||||
split_after_command_submit, ssh_destination_host, ssh_username, ssh_with_user,
|
||||
startup_command, status_ssh_target, toml_bare_key_or_quoted, update_check_requested,
|
||||
update_shell_command_line, valid_forward_host,
|
||||
};
|
||||
use dosh::config::{ClientConfig, CommandExtension, HostConfig};
|
||||
use dosh::native::EnvVar;
|
||||
@@ -5910,6 +6009,37 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_after_command_submit_holds_followup_input() {
|
||||
assert_eq!(split_after_command_submit(b"tm"), None);
|
||||
assert_eq!(
|
||||
split_after_command_submit(b"tm\r"),
|
||||
Some((b"tm\r".to_vec(), Vec::new()))
|
||||
);
|
||||
assert_eq!(
|
||||
split_after_command_submit(b"tm\r\x1b[B"),
|
||||
Some((b"tm\r".to_vec(), b"\x1b[B".to_vec()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_command_line_detects_tui_submit() {
|
||||
let mut line = Vec::new();
|
||||
assert!(!update_shell_command_line(&mut line, b"echo ok\r", false));
|
||||
assert!(line.is_empty());
|
||||
|
||||
assert!(!update_shell_command_line(&mut line, b"t", false));
|
||||
assert!(!update_shell_command_line(&mut line, b"m", false));
|
||||
assert!(update_shell_command_line(&mut line, b"\r", false));
|
||||
assert!(line.is_empty());
|
||||
|
||||
assert!(update_shell_command_line(
|
||||
&mut line,
|
||||
b" btop\r\x1b[B",
|
||||
false
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_check_accepts_only_check_flag() {
|
||||
assert!(!update_check_requested(&[]).unwrap());
|
||||
|
||||
Reference in New Issue
Block a user