commit
This commit is contained in:
@@ -1,519 +0,0 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ParsedEscape {
|
||||
SyncStart,
|
||||
SyncEnd,
|
||||
ClearScreen,
|
||||
ClearScrollback,
|
||||
CursorHome,
|
||||
CursorUp(u16),
|
||||
CursorCol(u16),
|
||||
ClearLine,
|
||||
Newline,
|
||||
CarriageReturn,
|
||||
Sgr(SgrCode),
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct SgrCode {
|
||||
pub reset: bool,
|
||||
pub fg: Option<Color>,
|
||||
pub bg: Option<Color>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Color {
|
||||
Default,
|
||||
Indexed(u8),
|
||||
Rgb(u8, u8, u8),
|
||||
}
|
||||
|
||||
pub struct EscapeParser {
|
||||
state: ParserState,
|
||||
params: Vec<u16>,
|
||||
intermediate: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ParserState {
|
||||
Ground,
|
||||
Escape,
|
||||
CsiEntry,
|
||||
CsiParam,
|
||||
CsiIntermediate,
|
||||
OscString,
|
||||
DcsString,
|
||||
}
|
||||
|
||||
impl Default for EscapeParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EscapeParser {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: ParserState::Ground,
|
||||
params: Vec::with_capacity(16),
|
||||
intermediate: Vec::with_capacity(4),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_escape_sequence(&self) -> bool {
|
||||
self.state != ParserState::Ground
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, byte: u8) -> Option<ParsedEscape> {
|
||||
match self.state {
|
||||
ParserState::Ground => self.ground(byte),
|
||||
ParserState::Escape => self.escape(byte),
|
||||
ParserState::CsiEntry => self.csi_entry(byte),
|
||||
ParserState::CsiParam => self.csi_param(byte),
|
||||
ParserState::CsiIntermediate => self.csi_intermediate(byte),
|
||||
ParserState::OscString => self.osc_string(byte),
|
||||
ParserState::DcsString => self.dcs_string(byte),
|
||||
}
|
||||
}
|
||||
|
||||
fn ground(&mut self, byte: u8) -> Option<ParsedEscape> {
|
||||
match byte {
|
||||
0x1b => {
|
||||
self.state = ParserState::Escape;
|
||||
None
|
||||
}
|
||||
b'\n' => Some(ParsedEscape::Newline),
|
||||
b'\r' => Some(ParsedEscape::CarriageReturn),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn escape(&mut self, byte: u8) -> Option<ParsedEscape> {
|
||||
match byte {
|
||||
b'[' => {
|
||||
self.state = ParserState::CsiEntry;
|
||||
self.params.clear();
|
||||
self.intermediate.clear();
|
||||
None
|
||||
}
|
||||
b']' => {
|
||||
self.state = ParserState::OscString;
|
||||
None
|
||||
}
|
||||
b'P' | b'^' | b'_' => {
|
||||
self.state = ParserState::DcsString;
|
||||
None
|
||||
}
|
||||
_ => {
|
||||
self.state = ParserState::Ground;
|
||||
Some(ParsedEscape::Other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn osc_string(&mut self, byte: u8) -> Option<ParsedEscape> {
|
||||
match byte {
|
||||
0x07 => {
|
||||
self.state = ParserState::Ground;
|
||||
Some(ParsedEscape::Other)
|
||||
}
|
||||
0x1b => {
|
||||
self.state = ParserState::Escape;
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn dcs_string(&mut self, byte: u8) -> Option<ParsedEscape> {
|
||||
match byte {
|
||||
0x1b => {
|
||||
self.state = ParserState::Escape;
|
||||
None
|
||||
}
|
||||
0x9c => {
|
||||
self.state = ParserState::Ground;
|
||||
Some(ParsedEscape::Other)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn csi_entry(&mut self, byte: u8) -> Option<ParsedEscape> {
|
||||
match byte {
|
||||
b'0'..=b'9' => {
|
||||
self.params.push((byte - b'0') as u16);
|
||||
self.state = ParserState::CsiParam;
|
||||
None
|
||||
}
|
||||
b';' => {
|
||||
self.params.push(0);
|
||||
self.state = ParserState::CsiParam;
|
||||
None
|
||||
}
|
||||
b'?' => {
|
||||
self.intermediate.push(byte);
|
||||
self.state = ParserState::CsiIntermediate;
|
||||
None
|
||||
}
|
||||
b'@'..=b'~' => {
|
||||
self.state = ParserState::Ground;
|
||||
self.dispatch_csi(byte)
|
||||
}
|
||||
_ => {
|
||||
self.state = ParserState::Ground;
|
||||
Some(ParsedEscape::Other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn csi_param(&mut self, byte: u8) -> Option<ParsedEscape> {
|
||||
match byte {
|
||||
b'0'..=b'9' => {
|
||||
if let Some(last) = self.params.last_mut() {
|
||||
*last = last.saturating_mul(10).saturating_add((byte - b'0') as u16);
|
||||
}
|
||||
None
|
||||
}
|
||||
b';' => {
|
||||
self.params.push(0);
|
||||
None
|
||||
}
|
||||
b'@'..=b'~' => {
|
||||
self.state = ParserState::Ground;
|
||||
self.dispatch_csi(byte)
|
||||
}
|
||||
_ => {
|
||||
self.state = ParserState::Ground;
|
||||
Some(ParsedEscape::Other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn csi_intermediate(&mut self, byte: u8) -> Option<ParsedEscape> {
|
||||
match byte {
|
||||
b'0'..=b'9' => {
|
||||
if self.params.is_empty() {
|
||||
self.params.push((byte - b'0') as u16);
|
||||
} else if let Some(last) = self.params.last_mut() {
|
||||
*last = last.saturating_mul(10).saturating_add((byte - b'0') as u16);
|
||||
}
|
||||
None
|
||||
}
|
||||
b';' => {
|
||||
self.params.push(0);
|
||||
None
|
||||
}
|
||||
b'@'..=b'~' => {
|
||||
self.state = ParserState::Ground;
|
||||
self.dispatch_private_csi(byte)
|
||||
}
|
||||
_ => {
|
||||
self.intermediate.push(byte);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_csi(&mut self, byte: u8) -> Option<ParsedEscape> {
|
||||
match byte {
|
||||
b'H' => {
|
||||
if self.params.is_empty()
|
||||
|| (self.params.len() == 2 && self.params[0] <= 1 && self.params[1] <= 1)
|
||||
{
|
||||
Some(ParsedEscape::CursorHome)
|
||||
} else {
|
||||
Some(ParsedEscape::Other)
|
||||
}
|
||||
}
|
||||
b'J' => {
|
||||
let param = self.params.first().copied().unwrap_or(0);
|
||||
match param {
|
||||
2 => Some(ParsedEscape::ClearScreen),
|
||||
3 => Some(ParsedEscape::ClearScrollback),
|
||||
_ => Some(ParsedEscape::Other),
|
||||
}
|
||||
}
|
||||
b'A' => {
|
||||
let n = self.params.first().copied().unwrap_or(1).max(1);
|
||||
Some(ParsedEscape::CursorUp(n))
|
||||
}
|
||||
b'G' => {
|
||||
let col = self.params.first().copied().unwrap_or(1);
|
||||
Some(ParsedEscape::CursorCol(col))
|
||||
}
|
||||
b'K' => Some(ParsedEscape::ClearLine),
|
||||
b'm' => Some(ParsedEscape::Sgr(self.parse_sgr())),
|
||||
_ => Some(ParsedEscape::Other),
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_private_csi(&mut self, byte: u8) -> Option<ParsedEscape> {
|
||||
if self.intermediate.first() == Some(&b'?') {
|
||||
let param = self.params.first().copied().unwrap_or(0);
|
||||
match (param, byte) {
|
||||
(2026, b'h') => Some(ParsedEscape::SyncStart),
|
||||
(2026, b'l') => Some(ParsedEscape::SyncEnd),
|
||||
_ => Some(ParsedEscape::Other),
|
||||
}
|
||||
} else {
|
||||
Some(ParsedEscape::Other)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sgr(&self) -> SgrCode {
|
||||
let mut sgr = SgrCode::default();
|
||||
let mut i = 0;
|
||||
while i < self.params.len() {
|
||||
match self.params[i] {
|
||||
0 => sgr.reset = true,
|
||||
38 => {
|
||||
if i + 1 < self.params.len() && self.params[i + 1] == 2 {
|
||||
if i + 4 < self.params.len() {
|
||||
let r = self.params[i + 2] as u8;
|
||||
let g = self.params[i + 3] as u8;
|
||||
let b = self.params[i + 4] as u8;
|
||||
sgr.fg = Some(Color::Rgb(r, g, b));
|
||||
i += 4;
|
||||
}
|
||||
} else if i + 1 < self.params.len()
|
||||
&& self.params[i + 1] == 5
|
||||
&& i + 2 < self.params.len()
|
||||
{
|
||||
sgr.fg = Some(Color::Indexed(self.params[i + 2] as u8));
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
48 => {
|
||||
if i + 1 < self.params.len() && self.params[i + 1] == 2 {
|
||||
if i + 4 < self.params.len() {
|
||||
let r = self.params[i + 2] as u8;
|
||||
let g = self.params[i + 3] as u8;
|
||||
let b = self.params[i + 4] as u8;
|
||||
sgr.bg = Some(Color::Rgb(r, g, b));
|
||||
i += 4;
|
||||
}
|
||||
} else if i + 1 < self.params.len()
|
||||
&& self.params[i + 1] == 5
|
||||
&& i + 2 < self.params.len()
|
||||
{
|
||||
sgr.bg = Some(Color::Indexed(self.params[i + 2] as u8));
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
39 => sgr.fg = Some(Color::Default),
|
||||
49 => sgr.bg = Some(Color::Default),
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
sgr
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn parse_sequence(bytes: &[u8]) -> Vec<ParsedEscape> {
|
||||
let mut parser = EscapeParser::new();
|
||||
bytes.iter().filter_map(|&b| parser.feed(b)).collect()
|
||||
}
|
||||
|
||||
fn parse_last(bytes: &[u8]) -> Option<ParsedEscape> {
|
||||
parse_sequence(bytes).into_iter().last()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_newline() {
|
||||
assert_eq!(parse_last(b"\n"), Some(ParsedEscape::Newline));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_carriage_return() {
|
||||
assert_eq!(parse_last(b"\r"), Some(ParsedEscape::CarriageReturn));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_screen() {
|
||||
assert_eq!(parse_last(b"\x1b[2J"), Some(ParsedEscape::ClearScreen));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_scrollback() {
|
||||
assert_eq!(parse_last(b"\x1b[3J"), Some(ParsedEscape::ClearScrollback));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cursor_home() {
|
||||
assert_eq!(parse_last(b"\x1b[H"), Some(ParsedEscape::CursorHome));
|
||||
assert_eq!(parse_last(b"\x1b[1;1H"), Some(ParsedEscape::CursorHome));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cursor_home_with_position() {
|
||||
assert_eq!(parse_last(b"\x1b[5;10H"), Some(ParsedEscape::Other));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cursor_up() {
|
||||
assert_eq!(parse_last(b"\x1b[A"), Some(ParsedEscape::CursorUp(1)));
|
||||
assert_eq!(parse_last(b"\x1b[5A"), Some(ParsedEscape::CursorUp(5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cursor_col() {
|
||||
assert_eq!(parse_last(b"\x1b[G"), Some(ParsedEscape::CursorCol(1)));
|
||||
assert_eq!(parse_last(b"\x1b[15G"), Some(ParsedEscape::CursorCol(15)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_line() {
|
||||
assert_eq!(parse_last(b"\x1b[K"), Some(ParsedEscape::ClearLine));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_start() {
|
||||
assert_eq!(parse_last(b"\x1b[?2026h"), Some(ParsedEscape::SyncStart));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_end() {
|
||||
assert_eq!(parse_last(b"\x1b[?2026l"), Some(ParsedEscape::SyncEnd));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sgr_reset() {
|
||||
let result = parse_last(b"\x1b[0m");
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(ParsedEscape::Sgr(SgrCode {
|
||||
reset: true,
|
||||
fg: None,
|
||||
bg: None,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sgr_default_colors() {
|
||||
let result = parse_last(b"\x1b[39;49m");
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(ParsedEscape::Sgr(SgrCode {
|
||||
reset: false,
|
||||
fg: Some(Color::Default),
|
||||
bg: Some(Color::Default),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sgr_indexed_fg() {
|
||||
let result = parse_last(b"\x1b[38;5;196m");
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(ParsedEscape::Sgr(SgrCode {
|
||||
reset: false,
|
||||
fg: Some(Color::Indexed(196)),
|
||||
bg: None,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sgr_indexed_bg() {
|
||||
let result = parse_last(b"\x1b[48;5;21m");
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(ParsedEscape::Sgr(SgrCode {
|
||||
reset: false,
|
||||
fg: None,
|
||||
bg: Some(Color::Indexed(21)),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sgr_rgb_fg() {
|
||||
let result = parse_last(b"\x1b[38;2;255;128;0m");
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(ParsedEscape::Sgr(SgrCode {
|
||||
reset: false,
|
||||
fg: Some(Color::Rgb(255, 128, 0)),
|
||||
bg: None,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sgr_rgb_bg() {
|
||||
let result = parse_last(b"\x1b[48;2;0;128;255m");
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(ParsedEscape::Sgr(SgrCode {
|
||||
reset: false,
|
||||
fg: None,
|
||||
bg: Some(Color::Rgb(0, 128, 255)),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_osc_title_bel_terminated() {
|
||||
let events = parse_sequence(b"\x1b]0;My Title\x07");
|
||||
assert_eq!(events, vec![ParsedEscape::Other]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_osc_title_st_terminated() {
|
||||
let events = parse_sequence(b"\x1b]0;My Title\x1b\\");
|
||||
assert_eq!(events, vec![ParsedEscape::Other]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dcs_sequence() {
|
||||
let events = parse_sequence(b"\x1bPsome data\x1b\\");
|
||||
assert_eq!(events, vec![ParsedEscape::Other]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apc_sequence() {
|
||||
let events = parse_sequence(b"\x1b_application data\x1b\\");
|
||||
assert_eq!(events, vec![ParsedEscape::Other]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pm_sequence() {
|
||||
let events = parse_sequence(b"\x1b^private message\x1b\\");
|
||||
assert_eq!(events, vec![ParsedEscape::Other]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixed_content() {
|
||||
let events = parse_sequence(b"hello\nworld\r\n");
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![
|
||||
ParsedEscape::Newline,
|
||||
ParsedEscape::CarriageReturn,
|
||||
ParsedEscape::Newline
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_followed_by_text() {
|
||||
let events = parse_sequence(b"\x1b[2Jhello\n");
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![ParsedEscape::ClearScreen, ParsedEscape::Newline]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unknown_csi() {
|
||||
assert_eq!(parse_last(b"\x1b[999z"), Some(ParsedEscape::Other));
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,5 @@ pub const CLEAR_SCROLLBACK: &[u8] = b"\x1b[3J";
|
||||
pub const CURSOR_HOME: &[u8] = b"\x1b[H";
|
||||
|
||||
pub const SYNC_BUFFER_CAPACITY: usize = 1024 * 1024;
|
||||
pub const PASSTHROUGH_BUFFER_CAPACITY: usize = 65536;
|
||||
pub const OUTPUT_BUFFER_CAPACITY: usize = 32768;
|
||||
pub const PENDING_ESCAPE_CAPACITY: usize = 32;
|
||||
pub const INPUT_BUFFER_CAPACITY: usize = 64;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod escape_parser;
|
||||
pub mod escape_sequences;
|
||||
pub mod key_parser;
|
||||
pub mod line_buffer;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use crate::escape_parser::{EscapeParser, ParsedEscape};
|
||||
use crate::escape_sequences::{
|
||||
CLEAR_SCREEN, CLEAR_SCROLLBACK, CURSOR_HOME, INPUT_BUFFER_CAPACITY, OUTPUT_BUFFER_CAPACITY,
|
||||
PASSTHROUGH_BUFFER_CAPACITY, PENDING_ESCAPE_CAPACITY, SYNC_BUFFER_CAPACITY, SYNC_END,
|
||||
SYNC_START,
|
||||
SYNC_BUFFER_CAPACITY, SYNC_END, SYNC_START,
|
||||
};
|
||||
use crate::line_buffer::LineBuffer;
|
||||
use anyhow::{Context, Result};
|
||||
@@ -59,11 +57,10 @@ pub struct Proxy {
|
||||
history: LineBuffer,
|
||||
sync_buffer: Vec<u8>,
|
||||
in_sync_block: bool,
|
||||
parser: EscapeParser,
|
||||
input_buffer: Vec<u8>,
|
||||
passthrough_buffer: Vec<u8>,
|
||||
output_buffer: Vec<u8>,
|
||||
pending_escape: Vec<u8>,
|
||||
sync_start_finder: memmem::Finder<'static>,
|
||||
sync_end_finder: memmem::Finder<'static>,
|
||||
clear_screen_finder: memmem::Finder<'static>,
|
||||
cursor_home_finder: memmem::Finder<'static>,
|
||||
}
|
||||
@@ -117,11 +114,10 @@ impl Proxy {
|
||||
original_termios,
|
||||
sync_buffer: Vec::with_capacity(SYNC_BUFFER_CAPACITY),
|
||||
in_sync_block: false,
|
||||
parser: EscapeParser::new(),
|
||||
input_buffer: Vec::with_capacity(INPUT_BUFFER_CAPACITY),
|
||||
passthrough_buffer: Vec::with_capacity(PASSTHROUGH_BUFFER_CAPACITY),
|
||||
output_buffer: Vec::with_capacity(OUTPUT_BUFFER_CAPACITY),
|
||||
pending_escape: Vec::with_capacity(PENDING_ESCAPE_CAPACITY),
|
||||
sync_start_finder: memmem::Finder::new(SYNC_START),
|
||||
sync_end_finder: memmem::Finder::new(SYNC_END),
|
||||
clear_screen_finder: memmem::Finder::new(CLEAR_SCREEN),
|
||||
cursor_home_finder: memmem::Finder::new(CURSOR_HOME),
|
||||
})
|
||||
@@ -201,76 +197,37 @@ impl Proxy {
|
||||
}
|
||||
|
||||
fn process_output(&mut self, data: &[u8], stdout_fd: i32) -> Result<()> {
|
||||
self.passthrough_buffer.clear();
|
||||
let mut pos = 0;
|
||||
|
||||
for &byte in data {
|
||||
let in_escape = self.parser.in_escape_sequence();
|
||||
|
||||
if in_escape && self.pending_escape.is_empty() {
|
||||
self.pending_escape.push(0x1b);
|
||||
}
|
||||
|
||||
if let Some(event) = self.parser.feed(byte) {
|
||||
match event {
|
||||
ParsedEscape::SyncStart => {
|
||||
if !self.passthrough_buffer.is_empty() {
|
||||
write_all_raw(stdout_fd, &self.passthrough_buffer)?;
|
||||
self.passthrough_buffer.clear();
|
||||
}
|
||||
self.pending_escape.clear();
|
||||
self.in_sync_block = true;
|
||||
self.sync_buffer.clear();
|
||||
self.sync_buffer.extend_from_slice(SYNC_START);
|
||||
continue;
|
||||
}
|
||||
ParsedEscape::SyncEnd => {
|
||||
self.pending_escape.clear();
|
||||
if self.in_sync_block {
|
||||
self.sync_buffer.extend_from_slice(SYNC_END);
|
||||
self.flush_sync_block(stdout_fd)?;
|
||||
self.in_sync_block = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
self.flush_pending_escape();
|
||||
}
|
||||
while pos < data.len() {
|
||||
if self.in_sync_block {
|
||||
if let Some(idx) = self.sync_end_finder.find(&data[pos..]) {
|
||||
self.sync_buffer.extend_from_slice(&data[pos..pos + idx]);
|
||||
self.sync_buffer.extend_from_slice(SYNC_END);
|
||||
self.flush_sync_block(stdout_fd)?;
|
||||
self.in_sync_block = false;
|
||||
pos += idx + SYNC_END.len();
|
||||
} else {
|
||||
self.sync_buffer.extend_from_slice(&data[pos..]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.parser.in_escape_sequence() && !self.pending_escape.is_empty() {
|
||||
self.flush_pending_escape();
|
||||
}
|
||||
|
||||
if self.parser.in_escape_sequence() {
|
||||
self.pending_escape.push(byte);
|
||||
} else if self.in_sync_block {
|
||||
self.sync_buffer.push(byte);
|
||||
} else if let Some(idx) = self.sync_start_finder.find(&data[pos..]) {
|
||||
if idx > 0 {
|
||||
write_all_raw(stdout_fd, &data[pos..pos + idx])?;
|
||||
}
|
||||
self.in_sync_block = true;
|
||||
self.sync_buffer.clear();
|
||||
self.sync_buffer.extend_from_slice(SYNC_START);
|
||||
pos += idx + SYNC_START.len();
|
||||
} else {
|
||||
self.passthrough_buffer.push(byte);
|
||||
write_all_raw(stdout_fd, &data[pos..])?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.pending_escape.is_empty() && !self.parser.in_escape_sequence() {
|
||||
self.flush_pending_escape();
|
||||
}
|
||||
|
||||
if !self.passthrough_buffer.is_empty() {
|
||||
write_all_raw(stdout_fd, &self.passthrough_buffer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush_pending_escape(&mut self) {
|
||||
if self.in_sync_block {
|
||||
self.sync_buffer.extend_from_slice(&self.pending_escape);
|
||||
} else {
|
||||
self.passthrough_buffer
|
||||
.extend_from_slice(&self.pending_escape);
|
||||
}
|
||||
self.pending_escape.clear();
|
||||
}
|
||||
|
||||
fn flush_sync_block(&mut self, stdout_fd: i32) -> Result<()> {
|
||||
let has_clear_screen = self.clear_screen_finder.find(&self.sync_buffer).is_some();
|
||||
let has_cursor_home = self.cursor_home_finder.find(&self.sync_buffer).is_some();
|
||||
@@ -278,13 +235,10 @@ impl Proxy {
|
||||
|
||||
if is_full_redraw {
|
||||
self.history.clear();
|
||||
let content_start = self.find_content_start();
|
||||
let content_end = self.sync_buffer.len().saturating_sub(SYNC_END.len());
|
||||
if content_start < content_end {
|
||||
self.history
|
||||
.push_bytes(&self.sync_buffer[content_start..content_end]);
|
||||
}
|
||||
}
|
||||
self.history.push_bytes(&self.sync_buffer);
|
||||
|
||||
if is_full_redraw {
|
||||
self.create_truncated_output();
|
||||
write_all_raw(stdout_fd, &self.output_buffer)?;
|
||||
} else {
|
||||
@@ -305,28 +259,6 @@ impl Proxy {
|
||||
self.output_buffer.extend_from_slice(SYNC_END);
|
||||
}
|
||||
|
||||
fn find_content_start(&self) -> usize {
|
||||
let sync_start_finder = memmem::Finder::new(SYNC_START);
|
||||
let clear_screen_finder = memmem::Finder::new(CLEAR_SCREEN);
|
||||
let clear_scrollback_finder = memmem::Finder::new(CLEAR_SCROLLBACK);
|
||||
let cursor_home_finder = memmem::Finder::new(CURSOR_HOME);
|
||||
|
||||
let mut pos = 0;
|
||||
if let Some(idx) = sync_start_finder.find(&self.sync_buffer[pos..]) {
|
||||
pos += idx + SYNC_START.len();
|
||||
}
|
||||
if let Some(idx) = clear_screen_finder.find(&self.sync_buffer[pos..]) {
|
||||
pos += idx + CLEAR_SCREEN.len();
|
||||
}
|
||||
if let Some(idx) = clear_scrollback_finder.find(&self.sync_buffer[pos..]) {
|
||||
pos += idx + CLEAR_SCROLLBACK.len();
|
||||
}
|
||||
if let Some(idx) = cursor_home_finder.find(&self.sync_buffer[pos..]) {
|
||||
pos += idx + CURSOR_HOME.len();
|
||||
}
|
||||
pos
|
||||
}
|
||||
|
||||
fn process_input(&mut self, data: &[u8], stdout_fd: i32) -> Result<()> {
|
||||
let master_fd = self.pty_master.as_raw_fd();
|
||||
for &byte in data {
|
||||
|
||||
Reference in New Issue
Block a user