esc handling fixed?
This commit is contained in:
+3
-3
@@ -634,13 +634,13 @@ pub fn render_box_char(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let h_thick = thickness(left.max(right));
|
|
||||||
let v_thick = thickness(up.max(down));
|
let v_thick = thickness(up.max(down));
|
||||||
|
let h_thick = thickness(left.max(right));
|
||||||
let h_extend = v_thick / 2;
|
let h_extend = v_thick / 2;
|
||||||
let v_extend = h_thick / 2;
|
let v_extend = h_thick / 2;
|
||||||
|
|
||||||
if left > 0 {
|
if left > 0 {
|
||||||
hline(&mut bitmap, 0, mid_x + h_extend + 1, mid_y, thickness(left));
|
hline(&mut bitmap, 0, mid_x + h_extend, mid_y, thickness(left));
|
||||||
}
|
}
|
||||||
if right > 0 {
|
if right > 0 {
|
||||||
hline(
|
hline(
|
||||||
@@ -652,7 +652,7 @@ pub fn render_box_char(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if up > 0 {
|
if up > 0 {
|
||||||
vline(&mut bitmap, 0, mid_y + v_extend + 1, mid_x, thickness(up));
|
vline(&mut bitmap, 0, mid_y + v_extend, mid_x, thickness(up));
|
||||||
}
|
}
|
||||||
if down > 0 {
|
if down > 0 {
|
||||||
vline(
|
vline(
|
||||||
|
|||||||
+16
-2
@@ -175,9 +175,10 @@ struct SpriteKey {
|
|||||||
|
|
||||||
impl SpriteKey {
|
impl SpriteKey {
|
||||||
/// Create a key for a single-cell sprite (the common case)
|
/// Create a key for a single-cell sprite (the common case)
|
||||||
|
/// Uses cell_index=255 as sentinel to distinguish from multi-cell cell 0
|
||||||
#[inline]
|
#[inline]
|
||||||
fn single(ch: char, style: FontStyle, colored: bool) -> Self {
|
fn single(ch: char, style: FontStyle, colored: bool) -> Self {
|
||||||
Self { ch, cell_index: 0, style, colored }
|
Self { ch, cell_index: 255, style, colored }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a key for a multi-cell sprite
|
/// Create a key for a multi-cell sprite
|
||||||
@@ -2267,6 +2268,12 @@ impl Renderer {
|
|||||||
// Regular character - create sprite as normal
|
// Regular character - create sprite as normal
|
||||||
let (sprite_idx, is_colored) = self.get_or_create_sprite(c, style);
|
let (sprite_idx, is_colored) = self.get_or_create_sprite(c, style);
|
||||||
|
|
||||||
|
// DEBUG: Log colored glyph detection
|
||||||
|
if is_colored {
|
||||||
|
log::debug!("EMOJI MULTICELL CHECK: col={} char=U+{:04X} '{}' sprite_idx={} is_colored={}",
|
||||||
|
col, c as u32, c, sprite_idx, is_colored);
|
||||||
|
}
|
||||||
|
|
||||||
// If this is a colored glyph (emoji) followed by empty cells, create multi-cell sprites
|
// If this is a colored glyph (emoji) followed by empty cells, create multi-cell sprites
|
||||||
if is_colored && sprite_idx != 0 {
|
if is_colored && sprite_idx != 0 {
|
||||||
// Count trailing empty cells for potential multi-cell emoji
|
// Count trailing empty cells for potential multi-cell emoji
|
||||||
@@ -2276,6 +2283,8 @@ impl Renderer {
|
|||||||
while col + num_empty + 1 < row.len() && num_empty < MAX_EXTRA_CELLS {
|
while col + num_empty + 1 < row.len() && num_empty < MAX_EXTRA_CELLS {
|
||||||
let next_cell = &row[col + num_empty + 1];
|
let next_cell = &row[col + num_empty + 1];
|
||||||
let next_char = next_cell.character;
|
let next_char = next_cell.character;
|
||||||
|
log::debug!(" checking next cell at col={}: char=U+{:04X} '{}' wide_cont={}",
|
||||||
|
col + num_empty + 1, next_char as u32, next_char, next_cell.wide_continuation);
|
||||||
if next_char == ' ' || next_char == '\u{2002}' || next_char == '\0' {
|
if next_char == ' ' || next_char == '\u{2002}' || next_char == '\0' {
|
||||||
num_empty += 1;
|
num_empty += 1;
|
||||||
} else {
|
} else {
|
||||||
@@ -2283,17 +2292,22 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::debug!(" found {} trailing empty cells", num_empty);
|
||||||
|
|
||||||
if num_empty > 0 {
|
if num_empty > 0 {
|
||||||
let total_cells = 1 + num_empty;
|
let total_cells = 1 + num_empty;
|
||||||
|
log::debug!(" creating multi-cell sprites for {} cells", total_cells);
|
||||||
|
|
||||||
// Check if we already have multi-cell sprites for this emoji
|
// Check if we already have multi-cell sprites for this emoji
|
||||||
let first_key = SpriteKey::multi(c, 0, style, true);
|
let first_key = SpriteKey::multi(c, 0, style, true);
|
||||||
|
|
||||||
if self.sprite_map.get(&first_key).is_none() {
|
if self.sprite_map.get(&first_key).is_none() {
|
||||||
// Need to create multi-cell emoji sprites
|
log::debug!(" rasterizing multi-cell emoji U+{:04X}", c as u32);
|
||||||
let cell_sprites = self.rasterize_emoji_multicell(c, total_cells);
|
let cell_sprites = self.rasterize_emoji_multicell(c, total_cells);
|
||||||
|
log::debug!(" got {} cell sprites", cell_sprites.len());
|
||||||
|
|
||||||
for (cell_idx, glyph) in cell_sprites.into_iter().enumerate() {
|
for (cell_idx, glyph) in cell_sprites.into_iter().enumerate() {
|
||||||
|
log::debug!(" cell {} sprite size: {:?}", cell_idx, glyph.size);
|
||||||
if glyph.size[0] > 0.0 && glyph.size[1] > 0.0 {
|
if glyph.size[0] > 0.0 && glyph.size[1] > 0.0 {
|
||||||
let key = SpriteKey::multi(c, cell_idx as u8, style, true);
|
let key = SpriteKey::multi(c, cell_idx as u8, style, true);
|
||||||
|
|
||||||
|
|||||||
+160
-180
@@ -240,8 +240,13 @@ struct BufferState {
|
|||||||
read_pos: usize,
|
read_pos: usize,
|
||||||
read_consumed: usize,
|
read_consumed: usize,
|
||||||
read_sz: usize,
|
read_sz: usize,
|
||||||
/// Write tracking: pending = bytes written by I/O but not yet visible to reader
|
/// Write tracking (like Kitty's write struct):
|
||||||
|
/// - pending: bytes written by I/O but not yet visible to reader
|
||||||
|
/// - offset: where I/O thread is writing (for compaction fixup)
|
||||||
|
/// - sz: size of current write buffer (0 if none outstanding)
|
||||||
write_pending: usize,
|
write_pending: usize,
|
||||||
|
write_offset: usize,
|
||||||
|
write_sz: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Kitty-style shared parser with integrated 1MB buffer.
|
/// Kitty-style shared parser with integrated 1MB buffer.
|
||||||
@@ -306,6 +311,8 @@ impl SharedParser {
|
|||||||
read_consumed: 0,
|
read_consumed: 0,
|
||||||
read_sz: 0,
|
read_sz: 0,
|
||||||
write_pending: 0,
|
write_pending: 0,
|
||||||
|
write_offset: 0,
|
||||||
|
write_sz: 0,
|
||||||
}),
|
}),
|
||||||
wakeup_fd,
|
wakeup_fd,
|
||||||
// Parser state - working copies for use while lock is released
|
// Parser state - working copies for use while lock is released
|
||||||
@@ -339,8 +346,17 @@ impl SharedParser {
|
|||||||
|
|
||||||
/// Get write buffer for I/O thread. Returns (ptr, available_bytes).
|
/// Get write buffer for I/O thread. Returns (ptr, available_bytes).
|
||||||
/// Caller MUST call commit_write() after writing.
|
/// Caller MUST call commit_write() after writing.
|
||||||
|
/// Like Kitty's vt_parser_create_write_buffer().
|
||||||
pub fn create_write_buffer(&self) -> (*mut u8, usize) {
|
pub fn create_write_buffer(&self) -> (*mut u8, usize) {
|
||||||
let state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
if state.write_sz > 0 {
|
||||||
|
log::error!(
|
||||||
|
"create_write_buffer called with existing write buffer"
|
||||||
|
);
|
||||||
|
return (std::ptr::null_mut(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
let write_offset = state.read_sz + state.write_pending;
|
let write_offset = state.read_sz + state.write_pending;
|
||||||
let available = BUF_SIZE.saturating_sub(write_offset);
|
let available = BUF_SIZE.saturating_sub(write_offset);
|
||||||
|
|
||||||
@@ -348,15 +364,33 @@ impl SharedParser {
|
|||||||
return (std::ptr::null_mut(), 0);
|
return (std::ptr::null_mut(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: I/O writes past read_sz+write_pending
|
state.write_offset = write_offset;
|
||||||
|
state.write_sz = available;
|
||||||
|
|
||||||
let ptr = unsafe { (*self.buf.get()).as_mut_ptr().add(write_offset) };
|
let ptr = unsafe { (*self.buf.get()).as_mut_ptr().add(write_offset) };
|
||||||
(ptr, available)
|
(ptr, available)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit bytes written by I/O thread.
|
/// Commit bytes written by I/O thread.
|
||||||
|
/// Like Kitty's vt_parser_commit_write() - handles compaction that happened
|
||||||
|
/// between create_write_buffer and commit_write by moving data if needed.
|
||||||
pub fn commit_write(&self, len: usize) {
|
pub fn commit_write(&self, len: usize) {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
|
let current_offset = state.read_sz + state.write_pending;
|
||||||
|
|
||||||
|
if state.write_offset > current_offset {
|
||||||
|
unsafe {
|
||||||
|
let buf = &mut *self.buf.get();
|
||||||
|
std::ptr::copy(
|
||||||
|
buf.as_ptr().add(state.write_offset),
|
||||||
|
buf.as_mut_ptr().add(current_offset),
|
||||||
|
len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state.write_pending += len;
|
state.write_pending += len;
|
||||||
|
state.write_sz = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from PTY fd into buffer. Returns bytes read, -1 for error.
|
/// Read from PTY fd into buffer. Returns bytes read, -1 for error.
|
||||||
@@ -371,10 +405,18 @@ impl SharedParser {
|
|||||||
|
|
||||||
if result > 0 {
|
if result > 0 {
|
||||||
self.commit_write(result as usize);
|
self.commit_write(result as usize);
|
||||||
|
} else {
|
||||||
|
self.cancel_write();
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cancel a pending write buffer (on error/EOF).
|
||||||
|
fn cancel_write(&self) {
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.write_sz = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// Drain the wakeup eventfd.
|
/// Drain the wakeup eventfd.
|
||||||
pub fn drain_wakeup(&self) {
|
pub fn drain_wakeup(&self) {
|
||||||
let mut buf = 0u64;
|
let mut buf = 0u64;
|
||||||
@@ -412,9 +454,6 @@ impl SharedParser {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let initial_pos = state.read_pos;
|
|
||||||
let initial_sz = state.read_sz;
|
|
||||||
|
|
||||||
// Track if buffer was ever full during this parse pass (for wakeup decision)
|
// Track if buffer was ever full during this parse pass (for wakeup decision)
|
||||||
// Like Kitty: pd->write_space_created = self->read.sz >= BUF_SZ (checked BEFORE compaction)
|
// Like Kitty: pd->write_space_created = self->read.sz >= BUF_SZ (checked BEFORE compaction)
|
||||||
let mut buffer_was_ever_full = state.read_sz >= BUF_SIZE;
|
let mut buffer_was_ever_full = state.read_sz >= BUF_SIZE;
|
||||||
@@ -422,25 +461,14 @@ impl SharedParser {
|
|||||||
// Reset consumed counter for this parse pass (like Kitty: self->read.consumed = 0)
|
// Reset consumed counter for this parse pass (like Kitty: self->read.consumed = 0)
|
||||||
state.read_consumed = 0;
|
state.read_consumed = 0;
|
||||||
|
|
||||||
// Check vte_state at start of parse pass
|
|
||||||
let vte_state_at_start = unsafe { *self.vte_state.get() };
|
|
||||||
if vte_state_at_start != State::Normal {
|
|
||||||
log::error!(
|
|
||||||
"DEBUG run_parse_pass START: vte_state={:?} read_pos={} read_sz={}",
|
|
||||||
vte_state_at_start, state.read_pos, state.read_sz
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy positions to UnsafeCell fields for use while lock is released
|
// Copy positions to UnsafeCell fields for use while lock is released
|
||||||
unsafe {
|
unsafe {
|
||||||
*self.parse_pos.get() = state.read_pos;
|
*self.parse_pos.get() = state.read_pos;
|
||||||
*self.parse_sz.get() = state.read_sz;
|
*self.parse_sz.get() = state.read_sz;
|
||||||
*self.parse_consumed.get() = state.read_pos; // consumed starts at current pos
|
*self.parse_consumed.get() = state.read_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse loop - release lock during parsing!
|
|
||||||
// Like Kitty's do { ... } while (self->read.pos < self->read.sz)
|
// Like Kitty's do { ... } while (self->read.pos < self->read.sz)
|
||||||
let mut loop_count = 0;
|
|
||||||
loop {
|
loop {
|
||||||
let parse_pos = unsafe { *self.parse_pos.get() };
|
let parse_pos = unsafe { *self.parse_pos.get() };
|
||||||
let parse_sz = unsafe { *self.parse_sz.get() };
|
let parse_sz = unsafe { *self.parse_sz.get() };
|
||||||
@@ -452,10 +480,9 @@ impl SharedParser {
|
|||||||
// RELEASE LOCK during parsing - I/O can continue writing!
|
// RELEASE LOCK during parsing - I/O can continue writing!
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
// Parse the data - consume_input updates parse_pos and parse_consumed
|
// Like Kitty line 1516: consume_input(self, ...)
|
||||||
self.consume_input(handler);
|
let made_progress = self.consume_input(handler);
|
||||||
parsed_any = true;
|
parsed_any = true;
|
||||||
loop_count += 1;
|
|
||||||
|
|
||||||
// Re-acquire lock
|
// Re-acquire lock
|
||||||
state = self.state.lock().unwrap();
|
state = self.state.lock().unwrap();
|
||||||
@@ -479,45 +506,14 @@ impl SharedParser {
|
|||||||
*self.parse_sz.get() = state.read_sz;
|
*self.parse_sz.get() = state.read_sz;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no more unparsed data, we're done (like Kitty: while read.pos < read.sz)
|
// Like Kitty: while read.pos < read.sz
|
||||||
if state.read_pos >= state.read_sz {
|
if state.read_pos >= state.read_sz || !made_progress {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes_parsed = state.read_pos.saturating_sub(initial_pos);
|
|
||||||
if bytes_parsed > 0 || loop_count > 1 {
|
|
||||||
log::debug!("[PARSE] initial_pos={} initial_sz={} final_pos={} final_sz={} loops={} bytes={}",
|
|
||||||
initial_pos, initial_sz, state.read_pos, state.read_sz, loop_count, bytes_parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compaction - remove consumed bytes (like Kitty)
|
// Compaction - remove consumed bytes (like Kitty)
|
||||||
if state.read_consumed > 0 {
|
if state.read_consumed > 0 {
|
||||||
let old_sz = state.read_sz;
|
|
||||||
|
|
||||||
// Debug: Check what we're about to discard and what state we're in
|
|
||||||
let vte_state = unsafe { *self.vte_state.get() };
|
|
||||||
if vte_state != State::Normal {
|
|
||||||
let buf = unsafe { &*self.buf.get() };
|
|
||||||
let remaining_start = state.read_consumed.min(old_sz);
|
|
||||||
let preview_len = (old_sz - remaining_start).min(20);
|
|
||||||
let preview: String = buf
|
|
||||||
[remaining_start..remaining_start + preview_len]
|
|
||||||
.iter()
|
|
||||||
.map(|&b| {
|
|
||||||
if b >= 0x20 && b < 0x7f {
|
|
||||||
b as char
|
|
||||||
} else {
|
|
||||||
'.'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
log::error!(
|
|
||||||
"DEBUG COMPACT: state={:?} consumed={} old_sz={} remaining preview: {:?}",
|
|
||||||
vte_state, state.read_consumed, old_sz, preview
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like Kitty: pos -= consumed, sz -= consumed, memmove
|
// Like Kitty: pos -= consumed, sz -= consumed, memmove
|
||||||
state.read_pos = state.read_pos.saturating_sub(state.read_consumed);
|
state.read_pos = state.read_pos.saturating_sub(state.read_consumed);
|
||||||
state.read_sz = state.read_sz.saturating_sub(state.read_consumed);
|
state.read_sz = state.read_sz.saturating_sub(state.read_consumed);
|
||||||
@@ -534,14 +530,11 @@ impl SharedParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let consumed = state.read_consumed;
|
|
||||||
state.read_consumed = 0;
|
state.read_consumed = 0;
|
||||||
|
|
||||||
// Wake I/O thread if buffer was ever full during this pass and we freed space
|
// Wake I/O thread if buffer was ever full during this pass and we freed space
|
||||||
// Like Kitty: if (pd.write_space_created) wakeup_io_loop()
|
// Like Kitty: if (pd.write_space_created) wakeup_io_loop()
|
||||||
if buffer_was_ever_full && state.read_sz < BUF_SIZE {
|
if buffer_was_ever_full && state.read_sz < BUF_SIZE {
|
||||||
log::debug!("[PARSE] Waking I/O: was_full={} old_sz={} new_sz={} consumed={}",
|
|
||||||
buffer_was_ever_full, old_sz, state.read_sz, consumed);
|
|
||||||
drop(state);
|
drop(state);
|
||||||
let val = 1u64;
|
let val = 1u64;
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -572,16 +565,14 @@ impl SharedParser {
|
|||||||
// ========== Internal parsing methods (main thread only) ==========
|
// ========== Internal parsing methods (main thread only) ==========
|
||||||
|
|
||||||
/// Main parsing dispatch - like Kitty's consume_input().
|
/// Main parsing dispatch - like Kitty's consume_input().
|
||||||
/// Reads from buf[parse_pos..parse_sz] and updates positions.
|
/// Processes ONE state case per call and returns, like Kitty lines 1458-1490.
|
||||||
|
/// The outer loop in run_parse_pass() calls this repeatedly until buffer exhausted.
|
||||||
///
|
///
|
||||||
/// IMPORTANT: Unlike the previous implementation, this now loops internally
|
/// Returns true if we made progress (consumed some bytes or changed state).
|
||||||
/// until the buffer is exhausted or we're waiting for more data in an incomplete
|
fn consume_input<H: Handler>(&self, handler: &mut H) -> bool {
|
||||||
/// escape sequence. This reduces per-CSI overhead from 3 function calls to 1.
|
|
||||||
fn consume_input<H: Handler>(&self, handler: &mut H) {
|
|
||||||
#[cfg(feature = "render_timing")]
|
#[cfg(feature = "render_timing")]
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
// Get mutable access to parser state (SAFETY: only main thread calls this)
|
|
||||||
let parse_pos = unsafe { &mut *self.parse_pos.get() };
|
let parse_pos = unsafe { &mut *self.parse_pos.get() };
|
||||||
let parse_sz = unsafe { *self.parse_sz.get() };
|
let parse_sz = unsafe { *self.parse_sz.get() };
|
||||||
let parse_consumed = unsafe { &mut *self.parse_consumed.get() };
|
let parse_consumed = unsafe { &mut *self.parse_consumed.get() };
|
||||||
@@ -594,131 +585,119 @@ impl SharedParser {
|
|||||||
let escape_len = unsafe { &mut *self.escape_len.get() };
|
let escape_len = unsafe { &mut *self.escape_len.get() };
|
||||||
let buf = unsafe { &*self.buf.get() };
|
let buf = unsafe { &*self.buf.get() };
|
||||||
|
|
||||||
// Debug: Log state at start of consume_input
|
if *parse_pos >= parse_sz {
|
||||||
if *vte_state != State::Normal {
|
#[cfg(feature = "render_timing")]
|
||||||
log::error!(
|
handler.add_vt_parser_ns(start.elapsed().as_nanos() as u64);
|
||||||
"DEBUG consume_input START: state={:?} pos={} consumed={} sz={}",
|
return false;
|
||||||
*vte_state, *parse_pos, *parse_consumed, parse_sz
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: Check if we're starting in Normal with CSI-like content
|
let made_progress = match *vte_state {
|
||||||
if *vte_state == State::Normal
|
State::Normal => {
|
||||||
&& *parse_pos < parse_sz
|
// Like Kitty line 1460: consume_normal(self); self->read.consumed = self->read.pos; break;
|
||||||
&& buf[*parse_pos] == b'['
|
Self::consume_normal_impl(
|
||||||
{
|
handler,
|
||||||
log::error!(
|
buf,
|
||||||
"DEBUG: Starting consume_input in Normal but buf[{}]='[' (0x5b). consumed={} sz={}",
|
parse_pos,
|
||||||
*parse_pos, *parse_consumed, parse_sz
|
parse_sz,
|
||||||
);
|
utf8,
|
||||||
}
|
codepoint_buf,
|
||||||
|
vte_state,
|
||||||
// Loop until buffer exhausted or waiting for more data
|
escape_len,
|
||||||
while *parse_pos < parse_sz {
|
);
|
||||||
match *vte_state {
|
*parse_consumed = *parse_pos;
|
||||||
State::Normal => {
|
true
|
||||||
Self::consume_normal_impl(
|
}
|
||||||
handler,
|
State::Escape => {
|
||||||
buf,
|
// Like Kitty lines 1461-1463:
|
||||||
parse_pos,
|
// case VTE_ESC: if (consume_esc(self)) { self->read.consumed = self->read.pos; } break;
|
||||||
parse_sz,
|
if Self::consume_escape_impl(
|
||||||
utf8,
|
handler,
|
||||||
codepoint_buf,
|
buf,
|
||||||
vte_state,
|
parse_pos,
|
||||||
escape_len,
|
parse_sz,
|
||||||
);
|
*parse_consumed,
|
||||||
|
vte_state,
|
||||||
|
csi,
|
||||||
|
osc_buffer,
|
||||||
|
string_buffer,
|
||||||
|
escape_len,
|
||||||
|
) {
|
||||||
*parse_consumed = *parse_pos;
|
*parse_consumed = *parse_pos;
|
||||||
}
|
}
|
||||||
State::Escape => {
|
true
|
||||||
let state_before = *vte_state;
|
}
|
||||||
if Self::consume_escape_impl(
|
State::EscapeIntermediate(_) => {
|
||||||
handler,
|
if Self::consume_escape_intermediate_impl(
|
||||||
buf,
|
handler, buf, parse_pos, parse_sz, vte_state,
|
||||||
parse_pos,
|
) {
|
||||||
parse_sz,
|
*parse_consumed = *parse_pos;
|
||||||
*parse_consumed,
|
|
||||||
vte_state,
|
|
||||||
csi,
|
|
||||||
osc_buffer,
|
|
||||||
string_buffer,
|
|
||||||
escape_len,
|
|
||||||
) {
|
|
||||||
*parse_consumed = *parse_pos;
|
|
||||||
} else if *vte_state != state_before {
|
|
||||||
// State changed to multi-byte sequence (CSI, OSC, etc.)
|
|
||||||
// Continue loop WITHOUT updating parse_consumed
|
|
||||||
} else {
|
|
||||||
// Need more data for escape sequence
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
State::EscapeIntermediate(_) => {
|
true
|
||||||
if Self::consume_escape_intermediate_impl(
|
}
|
||||||
handler, buf, parse_pos, parse_sz, vte_state,
|
State::Csi => {
|
||||||
) {
|
// Like Kitty lines 1465-1466:
|
||||||
*parse_consumed = *parse_pos;
|
// if (consume_csi(self)) { self->read.consumed = self->read.pos; if (self->csi.is_valid) dispatch_csi(self); SET_STATE(NORMAL); }
|
||||||
} else {
|
if Self::consume_csi_impl(
|
||||||
break;
|
handler,
|
||||||
}
|
buf,
|
||||||
}
|
parse_pos,
|
||||||
State::Csi => {
|
parse_sz,
|
||||||
// Like Kitty: if (consume_csi(self)) { self->read.consumed = self->read.pos; dispatch; SET_STATE(NORMAL); }
|
*parse_consumed,
|
||||||
if Self::consume_csi_impl(
|
csi,
|
||||||
handler,
|
escape_len,
|
||||||
buf,
|
) {
|
||||||
parse_pos,
|
*parse_consumed = *parse_pos;
|
||||||
parse_sz,
|
if csi.is_valid {
|
||||||
*parse_consumed,
|
handler.csi(csi);
|
||||||
csi,
|
|
||||||
escape_len,
|
|
||||||
) {
|
|
||||||
*parse_consumed = *parse_pos;
|
|
||||||
if csi.is_valid {
|
|
||||||
handler.csi(csi);
|
|
||||||
}
|
|
||||||
*vte_state = State::Normal;
|
|
||||||
// Continue loop to process more data
|
|
||||||
} else {
|
|
||||||
// Need more data for CSI sequence
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
State::Osc => {
|
|
||||||
if Self::consume_osc_impl(
|
|
||||||
handler, buf, parse_pos, parse_sz, vte_state,
|
|
||||||
osc_buffer, escape_len,
|
|
||||||
) {
|
|
||||||
*parse_consumed = *parse_pos;
|
|
||||||
*vte_state = State::Normal;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
State::Dcs | State::Apc | State::Pm | State::Sos => {
|
|
||||||
if Self::consume_string_impl(
|
|
||||||
handler,
|
|
||||||
buf,
|
|
||||||
parse_pos,
|
|
||||||
parse_sz,
|
|
||||||
vte_state,
|
|
||||||
string_buffer,
|
|
||||||
escape_len,
|
|
||||||
) {
|
|
||||||
*parse_consumed = *parse_pos;
|
|
||||||
*vte_state = State::Normal;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
*vte_state = State::Normal;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
State::Osc => {
|
||||||
|
if Self::consume_osc_impl(
|
||||||
|
handler, buf, parse_pos, parse_sz, vte_state, osc_buffer,
|
||||||
|
escape_len,
|
||||||
|
) {
|
||||||
|
*parse_consumed = *parse_pos;
|
||||||
|
*vte_state = State::Normal;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Dcs | State::Apc | State::Pm | State::Sos => {
|
||||||
|
if Self::consume_string_impl(
|
||||||
|
handler,
|
||||||
|
buf,
|
||||||
|
parse_pos,
|
||||||
|
parse_sz,
|
||||||
|
vte_state,
|
||||||
|
string_buffer,
|
||||||
|
escape_len,
|
||||||
|
) {
|
||||||
|
*parse_consumed = *parse_pos;
|
||||||
|
*vte_state = State::Normal;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "render_timing")]
|
#[cfg(feature = "render_timing")]
|
||||||
handler.add_vt_parser_ns(start.elapsed().as_nanos() as u64);
|
handler.add_vt_parser_ns(start.elapsed().as_nanos() as u64);
|
||||||
|
|
||||||
|
made_progress
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume normal text - like Kitty's consume_normal().
|
/// Consume normal text - like Kitty's consume_normal().
|
||||||
/// UTF-8 decodes until ESC is found using SIMD-optimized decoder.
|
/// UTF-8 decodes until ESC is found using SIMD-optimized decoder.
|
||||||
|
///
|
||||||
|
/// Like Kitty: processes text until ESC found, then sets state to Escape and returns.
|
||||||
|
/// The outer loop will call consume_input again to handle the Escape state.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn consume_normal_impl<H: Handler>(
|
fn consume_normal_impl<H: Handler>(
|
||||||
handler: &mut H,
|
handler: &mut H,
|
||||||
@@ -730,6 +709,7 @@ impl SharedParser {
|
|||||||
vte_state: &mut State,
|
vte_state: &mut State,
|
||||||
escape_len: &mut usize,
|
escape_len: &mut usize,
|
||||||
) {
|
) {
|
||||||
|
// Like Kitty's consume_normal() inner loop
|
||||||
loop {
|
loop {
|
||||||
if *parse_pos >= parse_sz {
|
if *parse_pos >= parse_sz {
|
||||||
break;
|
break;
|
||||||
@@ -738,18 +718,23 @@ impl SharedParser {
|
|||||||
let remaining = &buf[*parse_pos..parse_sz];
|
let remaining = &buf[*parse_pos..parse_sz];
|
||||||
let (consumed, found_esc) =
|
let (consumed, found_esc) =
|
||||||
utf8.decode_to_esc(remaining, codepoint_buf);
|
utf8.decode_to_esc(remaining, codepoint_buf);
|
||||||
*parse_pos += consumed;
|
|
||||||
|
|
||||||
if !codepoint_buf.is_empty() {
|
if !codepoint_buf.is_empty() {
|
||||||
handler.text(codepoint_buf);
|
handler.text(codepoint_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Like Kitty: self->read.pos += self->utf8_decoder.num_consumed
|
||||||
|
*parse_pos += consumed;
|
||||||
|
|
||||||
if found_esc {
|
if found_esc {
|
||||||
|
// Like Kitty: if (sentinel_found) { SET_STATE(ESC); break; }
|
||||||
*vte_state = State::Escape;
|
*vte_state = State::Escape;
|
||||||
*escape_len = 0;
|
*escape_len = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Like Kitty line 1460: consume_normal(self); self->read.consumed = self->read.pos; break;
|
||||||
|
// The caller (consume_input) will set parse_consumed = parse_pos
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume escape sequence start - like Kitty's consume_esc().
|
/// Consume escape sequence start - like Kitty's consume_esc().
|
||||||
@@ -786,33 +771,28 @@ impl SharedParser {
|
|||||||
b'[' => {
|
b'[' => {
|
||||||
*vte_state = State::Csi;
|
*vte_state = State::Csi;
|
||||||
csi.reset();
|
csi.reset();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
b']' => {
|
b']' => {
|
||||||
*vte_state = State::Osc;
|
*vte_state = State::Osc;
|
||||||
osc_buffer.clear();
|
osc_buffer.clear();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
b'P' => {
|
b'P' => {
|
||||||
*vte_state = State::Dcs;
|
*vte_state = State::Dcs;
|
||||||
string_buffer.clear();
|
string_buffer.clear();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
b'_' => {
|
b'_' => {
|
||||||
*vte_state = State::Apc;
|
*vte_state = State::Apc;
|
||||||
string_buffer.clear();
|
string_buffer.clear();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
b'^' => {
|
b'^' => {
|
||||||
*vte_state = State::Pm;
|
*vte_state = State::Pm;
|
||||||
string_buffer.clear();
|
string_buffer.clear();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
b'X' => {
|
b'X' => {
|
||||||
*vte_state = State::Sos;
|
*vte_state = State::Sos;
|
||||||
string_buffer.clear();
|
string_buffer.clear();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
// Two-byte escape sequences - return false like Kitty's IS_ESCAPED_CHAR
|
||||||
b'(' | b')' | b'*' | b'+' | b'-' | b'.' | b'/' | b'%'
|
b'(' | b')' | b'*' | b'+' | b'-' | b'.' | b'/' | b'%'
|
||||||
| b'#' | b' ' => {
|
| b'#' | b' ' => {
|
||||||
*vte_state = State::EscapeIntermediate(ch);
|
*vte_state = State::EscapeIntermediate(ch);
|
||||||
|
|||||||
Reference in New Issue
Block a user