esc handling progress
This commit is contained in:
+63
-6
@@ -381,10 +381,29 @@ fn vs_cell_bg(
|
||||
let col = instance_index % grid_params.cols;
|
||||
let row = instance_index / grid_params.cols;
|
||||
|
||||
// Skip if out of bounds
|
||||
// Skip if out of bounds - place vertex outside clip volume (z=2 is beyond far plane)
|
||||
if row >= grid_params.rows {
|
||||
var out: CellVertexOutput;
|
||||
out.clip_position = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.clip_position = vec4<f32>(0.0, 0.0, 2.0, 1.0);
|
||||
out.uv = vec2<f32>(0.0, 0.0);
|
||||
out.fg_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.bg_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.is_background = 1u;
|
||||
out.is_colored_glyph = 0u;
|
||||
out.is_cursor = 0u;
|
||||
out.cursor_shape = 0u;
|
||||
out.cursor_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.cursor_uv = vec2<f32>(0.0, 0.0);
|
||||
out.cell_size = vec2<f32>(0.0, 0.0);
|
||||
out.underline_uv = vec2<f32>(0.0, 0.0);
|
||||
out.strike_uv = vec2<f32>(0.0, 0.0);
|
||||
out.decoration_fg = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.has_underline = 0u;
|
||||
out.has_strikethrough = 0u;
|
||||
out.glyph_layer = 0;
|
||||
out.cursor_layer = 0;
|
||||
out.underline_layer = 0;
|
||||
out.strike_layer = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -517,10 +536,29 @@ fn vs_cell_glyph(
|
||||
let col = instance_index % grid_params.cols;
|
||||
let row = instance_index / grid_params.cols;
|
||||
|
||||
// Skip if out of bounds
|
||||
// Skip if out of bounds - use off-screen position with valid W to avoid undefined behavior
|
||||
if row >= grid_params.rows {
|
||||
var out: CellVertexOutput;
|
||||
out.clip_position = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.clip_position = vec4<f32>(0.0, 0.0, 2.0, 1.0);
|
||||
out.uv = vec2<f32>(0.0, 0.0);
|
||||
out.fg_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.bg_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.is_background = 0u;
|
||||
out.is_colored_glyph = 0u;
|
||||
out.is_cursor = 0u;
|
||||
out.cursor_shape = 0u;
|
||||
out.cursor_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.cursor_uv = vec2<f32>(0.0, 0.0);
|
||||
out.cell_size = vec2<f32>(0.0, 0.0);
|
||||
out.underline_uv = vec2<f32>(0.0, 0.0);
|
||||
out.strike_uv = vec2<f32>(0.0, 0.0);
|
||||
out.decoration_fg = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.has_underline = 0u;
|
||||
out.has_strikethrough = 0u;
|
||||
out.glyph_layer = 0;
|
||||
out.cursor_layer = 0;
|
||||
out.underline_layer = 0;
|
||||
out.strike_layer = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -536,10 +574,29 @@ fn vs_cell_glyph(
|
||||
let underline_sprite_idx = decoration_type_to_sprite(decoration_type);
|
||||
let has_decorations = underline_sprite_idx > 0u || has_strike;
|
||||
|
||||
// Skip if no glyph AND no decorations
|
||||
// Skip if no glyph AND no decorations - use off-screen position with valid W
|
||||
if sprite_idx == 0u && !has_decorations {
|
||||
var out: CellVertexOutput;
|
||||
out.clip_position = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.clip_position = vec4<f32>(0.0, 0.0, 2.0, 1.0);
|
||||
out.uv = vec2<f32>(0.0, 0.0);
|
||||
out.fg_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.bg_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.is_background = 0u;
|
||||
out.is_colored_glyph = 0u;
|
||||
out.is_cursor = 0u;
|
||||
out.cursor_shape = 0u;
|
||||
out.cursor_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.cursor_uv = vec2<f32>(0.0, 0.0);
|
||||
out.cell_size = vec2<f32>(0.0, 0.0);
|
||||
out.underline_uv = vec2<f32>(0.0, 0.0);
|
||||
out.strike_uv = vec2<f32>(0.0, 0.0);
|
||||
out.decoration_fg = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
out.has_underline = 0u;
|
||||
out.has_strikethrough = 0u;
|
||||
out.glyph_layer = 0;
|
||||
out.cursor_layer = 0;
|
||||
out.underline_layer = 0;
|
||||
out.strike_layer = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -2038,7 +2038,14 @@ impl App {
|
||||
self.config.edge_glow_intensity,
|
||||
&statusline_content,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Ok(_) => {
|
||||
// Clear dirty lines after successful render (like Kitty's linebuf_mark_line_clean)
|
||||
for (pane_id, _) in &geometries {
|
||||
if let Some(pane) = tab.panes.get_mut(pane_id) {
|
||||
pane.terminal.clear_dirty_lines();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(wgpu::SurfaceError::Lost) => {
|
||||
renderer.resize(renderer.width, renderer.height);
|
||||
}
|
||||
|
||||
+83
-37
@@ -2148,6 +2148,10 @@ impl Renderer {
|
||||
let rows = terminal.rows;
|
||||
let total_cells = cols * rows;
|
||||
|
||||
// TEMPORARY DEBUG: Force full rebuild every frame to test if dirty-line tracking is the issue
|
||||
// TODO: Remove this once the rendering bug is fixed
|
||||
self.cells_dirty = true;
|
||||
|
||||
// Check if grid size changed - need full rebuild
|
||||
let size_changed = self.last_grid_size != (cols, rows);
|
||||
if size_changed {
|
||||
@@ -2156,10 +2160,6 @@ impl Renderer {
|
||||
self.cells_dirty = true;
|
||||
}
|
||||
|
||||
// Get dirty lines bitmap BEFORE first pass - we only need to create sprites for dirty lines
|
||||
// This is a key optimization: sprites are cached, so we only need to check lines that changed
|
||||
let dirty_bitmap = terminal.get_dirty_lines();
|
||||
|
||||
// First pass: ensure all characters have sprites
|
||||
// This needs mutable access to self for sprite creation
|
||||
// Like Kitty's render_line(), detect PUA+space patterns for multi-cell rendering
|
||||
@@ -2167,18 +2167,8 @@ impl Renderer {
|
||||
// OPTIMIZATION: Use get_visible_row() to avoid Vec allocation
|
||||
for row_idx in 0..rows {
|
||||
// Skip clean lines (unless size changed, which sets cells_dirty)
|
||||
if !self.cells_dirty {
|
||||
if row_idx < 64 {
|
||||
let bit = 1u64 << row_idx;
|
||||
if (dirty_bitmap & bit) == 0 {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// For rows >= 64, we conservatively process them if any dirty bit is set
|
||||
// (same as the second pass behavior)
|
||||
else if dirty_bitmap == 0 {
|
||||
continue;
|
||||
}
|
||||
if !self.cells_dirty && !terminal.is_line_dirty(row_idx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(row) = terminal.get_visible_row(row_idx) else {
|
||||
@@ -2339,37 +2329,53 @@ impl Renderer {
|
||||
|
||||
// Second pass: convert cells to GPU format
|
||||
// OPTIMIZATION: Use get_visible_row() to avoid Vec allocation
|
||||
// dirty_bitmap already fetched above before first pass
|
||||
let mut any_updated = false;
|
||||
|
||||
// DEBUG: Log grid dimensions and buffer state
|
||||
static DEBUG_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
let frame_num = DEBUG_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
if frame_num % 60 == 0 { // Log every 60 frames (~1 second at 60fps)
|
||||
log::info!("DEBUG update_gpu_cells: cols={} rows={} total={} gpu_cells.len={} cells_dirty={}",
|
||||
cols, rows, total_cells, self.gpu_cells.len(), self.cells_dirty);
|
||||
}
|
||||
|
||||
// If we did a full reset or size changed, update all lines
|
||||
if self.cells_dirty {
|
||||
static ROW_DEBUG_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
let row_frame = ROW_DEBUG_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
if row_frame % 60 == 0 {
|
||||
let first_col: String = (0..rows).filter_map(|r| {
|
||||
terminal.get_visible_row(r).and_then(|row| {
|
||||
row.first().map(|cell| {
|
||||
let c = cell.character;
|
||||
if c == '\0' { ' ' } else { c }
|
||||
})
|
||||
})
|
||||
}).collect();
|
||||
log::info!("DEBUG col0: \"{}\"", first_col);
|
||||
}
|
||||
|
||||
for row_idx in 0..rows {
|
||||
if let Some(row) = terminal.get_visible_row(row_idx) {
|
||||
let start = row_idx * cols;
|
||||
let end = start + cols;
|
||||
|
||||
if end > self.gpu_cells.len() {
|
||||
log::error!("DEBUG BUG: row_idx={} start={} end={} but gpu_cells.len={}",
|
||||
row_idx, start, end, self.gpu_cells.len());
|
||||
continue;
|
||||
}
|
||||
|
||||
Self::cells_to_gpu_row_static(row, &mut self.gpu_cells[start..end], cols, &self.sprite_map);
|
||||
}
|
||||
}
|
||||
self.cells_dirty = false;
|
||||
any_updated = true;
|
||||
} else {
|
||||
// Only update dirty lines
|
||||
for row_idx in 0..rows.min(64) {
|
||||
let bit = 1u64 << row_idx;
|
||||
if (dirty_bitmap & bit) != 0 {
|
||||
if let Some(row) = terminal.get_visible_row(row_idx) {
|
||||
let start = row_idx * cols;
|
||||
let end = start + cols;
|
||||
Self::cells_to_gpu_row_static(row, &mut self.gpu_cells[start..end], cols, &self.sprite_map);
|
||||
any_updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For terminals with more than 64 rows, check additional dirty_lines words
|
||||
if rows > 64 && dirty_bitmap != 0 {
|
||||
for row_idx in 64..rows {
|
||||
// Only update dirty lines - use is_line_dirty() which handles all 256 lines
|
||||
for row_idx in 0..rows {
|
||||
if terminal.is_line_dirty(row_idx) {
|
||||
if let Some(row) = terminal.get_visible_row(row_idx) {
|
||||
let start = row_idx * cols;
|
||||
let end = start + cols;
|
||||
@@ -4601,9 +4607,8 @@ impl Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate pane dimensions in cells
|
||||
let cols = (pane_width / self.cell_metrics.cell_width as f32).floor() as u32;
|
||||
let rows = (pane_height / self.cell_metrics.cell_height as f32).floor() as u32;
|
||||
let cols = terminal.cols as u32;
|
||||
let rows = terminal.rows as u32;
|
||||
|
||||
// Use the actual gpu_cells size for buffer allocation (terminal.cols * terminal.rows)
|
||||
// This may differ from pane pixel dimensions due to rounding
|
||||
@@ -4638,6 +4643,35 @@ impl Renderer {
|
||||
selection_end_row: sel_end_row,
|
||||
};
|
||||
|
||||
// DEBUG: Log grid params every 60 frames
|
||||
static PANE_DEBUG_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
let pane_frame = PANE_DEBUG_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
if pane_frame % 60 == 0 {
|
||||
log::info!("DEBUG pane {}: grid_params cols={} rows={} gpu_cells.len={} expected={}",
|
||||
info.pane_id, grid_params.cols, grid_params.rows,
|
||||
self.gpu_cells.len(), (grid_params.cols * grid_params.rows) as usize);
|
||||
|
||||
// Sample a few cells to see if sprite indices look reasonable
|
||||
if !self.gpu_cells.is_empty() {
|
||||
let sample_indices = [0, 1, 2, cols as usize, cols as usize + 1];
|
||||
for &idx in &sample_indices {
|
||||
if idx < self.gpu_cells.len() {
|
||||
let cell = &self.gpu_cells[idx];
|
||||
let sprite_idx = cell.sprite_idx & !0x80000000;
|
||||
log::info!("DEBUG cell[{}]: sprite_idx={} fg={:#x} bg={:#x}",
|
||||
idx, sprite_idx, cell.fg, cell.bg);
|
||||
|
||||
if sprite_idx > 0 && (sprite_idx as usize) < self.sprite_info.len() {
|
||||
let sprite = &self.sprite_info[sprite_idx as usize];
|
||||
log::info!("DEBUG sprite[{}]: uv=({:.3},{:.3},{:.3},{:.3}) layer={} size=({:.1},{:.1})",
|
||||
sprite_idx, sprite.uv[0], sprite.uv[1], sprite.uv[2], sprite.uv[3],
|
||||
sprite.layer, sprite.size[0], sprite.size[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upload this pane's cell data to its own buffer (like Kitty's send_cell_data_to_gpu)
|
||||
// This happens BEFORE the render pass, so each pane has its own data
|
||||
if let Some(pane_res) = self.pane_resources.get(&info.pane_id) {
|
||||
@@ -5027,6 +5061,17 @@ impl Renderer {
|
||||
let (vp_x, vp_y, vp_w, vp_h) = pane_data.viewport;
|
||||
render_pass.set_viewport(vp_x, vp_y, vp_w, vp_h, 0.0, 1.0);
|
||||
|
||||
// Set scissor rect to clip rendering to pane bounds
|
||||
let scissor_x = (vp_x.round().max(0.0) as u32).min(self.width);
|
||||
let scissor_y = (vp_y.round().max(0.0) as u32).min(self.height);
|
||||
let scissor_w = (vp_w.round() as u32).min(self.width.saturating_sub(scissor_x));
|
||||
let scissor_h = (vp_h.round() as u32).min(self.height.saturating_sub(scissor_y));
|
||||
|
||||
if scissor_w == 0 || scissor_h == 0 {
|
||||
continue;
|
||||
}
|
||||
render_pass.set_scissor_rect(scissor_x, scissor_y, scissor_w, scissor_h);
|
||||
|
||||
// Draw cell backgrounds
|
||||
render_pass.set_pipeline(&self.cell_bg_pipeline);
|
||||
render_pass.set_bind_group(0, &self.glyph_bind_group, &[]); // Atlas (shared)
|
||||
@@ -5041,8 +5086,9 @@ impl Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Restore full-screen viewport for remaining rendering (statusline, overlays)
|
||||
// Restore full-screen viewport and scissor for remaining rendering (statusline, overlays)
|
||||
render_pass.set_viewport(0.0, 0.0, self.width as f32, self.height as f32, 0.0, 1.0);
|
||||
render_pass.set_scissor_rect(0, 0, self.width, self.height);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// STATUSLINE RENDERING (dedicated shader)
|
||||
|
||||
+51
-6
@@ -252,6 +252,11 @@ impl ColorPalette {
|
||||
}
|
||||
|
||||
/// Saved cursor state for DECSC/DECRC.
|
||||
/// Per ECMA-48 and DEC VT standards, DECSC saves:
|
||||
/// - Cursor position (col, row)
|
||||
/// - Character attributes (fg, bg, bold, italic, underline, strikethrough)
|
||||
/// - Origin mode (DECOM) - affects cursor positioning relative to scroll region
|
||||
/// - Auto-wrap mode (DECAWM) - affects line wrapping behavior
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct SavedCursor {
|
||||
col: usize,
|
||||
@@ -262,6 +267,8 @@ struct SavedCursor {
|
||||
italic: bool,
|
||||
underline_style: u8,
|
||||
strikethrough: bool,
|
||||
origin_mode: bool,
|
||||
auto_wrap: bool,
|
||||
}
|
||||
|
||||
/// Alternate screen buffer state.
|
||||
@@ -1374,6 +1381,26 @@ impl Handler for Terminal {
|
||||
/// Handle a chunk of decoded text (Unicode codepoints as u32).
|
||||
/// This includes control characters (0x00-0x1F except ESC).
|
||||
fn text(&mut self, codepoints: &[u32]) {
|
||||
// DEBUG: Detect CSI sequence content appearing as text (indicates parser bug)
|
||||
// Look for patterns like "38;2;128" or "4;64;64m" - these are SGR parameters
|
||||
if codepoints.len() >= 3 {
|
||||
let has_semicolon = codepoints.iter().any(|&c| c == 0x3B); // ';'
|
||||
let has_m = codepoints.iter().any(|&c| c == 0x6D); // 'm'
|
||||
let mostly_digits = codepoints
|
||||
.iter()
|
||||
.filter(|&&c| c >= 0x30 && c <= 0x39)
|
||||
.count()
|
||||
> codepoints.len() / 2;
|
||||
if has_semicolon && mostly_digits {
|
||||
let text: String = codepoints
|
||||
.iter()
|
||||
.filter_map(|&c| char::from_u32(c))
|
||||
.collect();
|
||||
log::error!("DEBUG CSI LEAK: text handler received CSI-like content: {:?} at ({}, {})",
|
||||
text, self.cursor_col, self.cursor_row);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render_timing")]
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
@@ -1866,6 +1893,11 @@ impl Handler for Terminal {
|
||||
match mode {
|
||||
0 => self.clear_line_from_cursor(),
|
||||
1 => {
|
||||
log::warn!(
|
||||
"DEBUG EL1 (erase to cursor): row={} cursor_col={}",
|
||||
self.cursor_row,
|
||||
self.cursor_col
|
||||
);
|
||||
let grid_row = self.line_map[self.cursor_row];
|
||||
for col in 0..=self.cursor_col {
|
||||
self.grid[grid_row][col] = blank;
|
||||
@@ -1873,6 +1905,10 @@ impl Handler for Terminal {
|
||||
self.mark_line_dirty(self.cursor_row);
|
||||
}
|
||||
2 => {
|
||||
log::warn!(
|
||||
"DEBUG EL2 (erase whole line): row={}",
|
||||
self.cursor_row
|
||||
);
|
||||
let grid_row = self.line_map[self.cursor_row];
|
||||
self.grid[grid_row].fill(blank);
|
||||
self.mark_line_dirty(self.cursor_row);
|
||||
@@ -2028,8 +2064,9 @@ impl Handler for Terminal {
|
||||
&mut self.scroll_bottom,
|
||||
);
|
||||
}
|
||||
// Move cursor to home position
|
||||
self.cursor_row = 0;
|
||||
// Move cursor to home position (respects origin mode)
|
||||
self.cursor_row =
|
||||
if self.origin_mode { self.scroll_top } else { 0 };
|
||||
self.cursor_col = 0;
|
||||
}
|
||||
// Window manipulation (CSI Ps t) - XTWINOPS
|
||||
@@ -2130,11 +2167,15 @@ impl Handler for Terminal {
|
||||
italic: self.current_italic,
|
||||
underline_style: self.current_underline_style,
|
||||
strikethrough: self.current_strikethrough,
|
||||
origin_mode: self.origin_mode,
|
||||
auto_wrap: self.auto_wrap,
|
||||
};
|
||||
log::debug!(
|
||||
"ESC 7: Cursor saved at ({}, {})",
|
||||
"ESC 7: Cursor saved at ({}, {}), origin_mode={}, auto_wrap={}",
|
||||
self.cursor_col,
|
||||
self.cursor_row
|
||||
self.cursor_row,
|
||||
self.origin_mode,
|
||||
self.auto_wrap
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2149,10 +2190,14 @@ impl Handler for Terminal {
|
||||
self.current_italic = self.saved_cursor.italic;
|
||||
self.current_underline_style = self.saved_cursor.underline_style;
|
||||
self.current_strikethrough = self.saved_cursor.strikethrough;
|
||||
self.origin_mode = self.saved_cursor.origin_mode;
|
||||
self.auto_wrap = self.saved_cursor.auto_wrap;
|
||||
log::debug!(
|
||||
"ESC 8: Cursor restored to ({}, {})",
|
||||
"ESC 8: Cursor restored to ({}, {}), origin_mode={}, auto_wrap={}",
|
||||
self.cursor_col,
|
||||
self.cursor_row
|
||||
self.cursor_row,
|
||||
self.origin_mode,
|
||||
self.auto_wrap
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+559
-311
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user