use super::FrameRenderData;
use super::claude_code_bridge::ClaudeCodePrettifierBridge;
use super::tab_snapshot;
use crate::app::window_state::WindowState;
impl WindowState {
pub(super) fn gather_render_data(&mut self) -> Option<FrameRenderData> {
let (renderer_size, visible_lines, grid_cols) = self.gather_viewport_sizing()?;
let (
terminal,
scroll_offset,
mouse_selection,
cache_cells,
cache_generation,
cache_scroll_offset,
cache_cursor_pos,
cache_selection,
cached_scrollback_len,
cached_terminal_title,
hovered_url,
) = match self.tab_manager.active_tab() {
Some(t) => (
t.terminal.clone(),
t.active_scroll_state().offset,
t.selection_mouse().selection,
t.active_cache().cells.clone(),
t.active_cache().generation,
t.active_cache().scroll_offset,
t.active_cache().cursor_pos,
t.active_cache().selection,
t.active_cache().scrollback_len,
t.active_cache().terminal_title.clone(),
t.active_mouse().hovered_url.clone(),
),
None => return None,
};
let _is_running = if let Ok(term) = terminal.try_write() {
term.is_running()
} else {
true };
let was_alt_screen = self
.tab_manager
.active_tab()
.map(|t| t.was_alt_screen)
.unwrap_or(false);
let snap = self.extract_tab_cells(tab_snapshot::TabCellsParams {
scroll_offset,
mouse_selection,
cache_cells,
cache_generation,
cache_scroll_offset,
cache_cursor_pos,
cache_selection,
terminal: terminal.clone(),
was_alt_screen,
})?;
let mut cells = snap.cells;
let current_cursor_pos = snap.cursor_pos;
let cursor_style = snap.cursor_style;
let is_alt_screen = snap.is_alt_screen;
let current_generation = snap.current_generation;
self.sync_prettifier_state(is_alt_screen);
let hide_cursor_for_shader = self.resolve_cursor_shader_hide(is_alt_screen);
if let Some(renderer) = &mut self.renderer {
renderer.set_cursor_hidden_for_shader(hide_cursor_for_shader);
}
self.flush_cell_cache(&cells, current_cursor_pos);
let mut show_scrollbar = self.should_show_scrollbar();
let (scrollback_len, terminal_title, shell_lifecycle_events) = self
.collect_scrollback_state(
&terminal,
current_cursor_pos,
cached_scrollback_len,
&cached_terminal_title,
);
let prettifier_block_count_before = self
.tab_manager
.active_tab()
.and_then(|t| t.prettifier.as_ref())
.map(|p| p.active_blocks().len())
.unwrap_or(0);
if !shell_lifecycle_events.is_empty()
&& let Some(tab) = self.tab_manager.active_tab_mut()
&& let Some(ref mut pipeline) = tab.prettifier
{
for event in &shell_lifecycle_events {
match event {
par_term_terminal::ShellLifecycleEvent::CommandStarted {
command,
absolute_line,
} => {
if let Some(ref mut pm) = tab.pane_manager
&& let Some(pane) = pm.focused_pane_mut()
{
pane.cache.prettifier_command_start_line = Some(*absolute_line);
pane.cache.prettifier_command_text = Some(command.clone());
}
pipeline.on_command_start(command);
}
par_term_terminal::ShellLifecycleEvent::CommandFinished { absolute_line } => {
let (start, cmd_text) = if let Some(ref mut pm) = tab.pane_manager
&& let Some(pane) = pm.focused_pane_mut()
{
(
pane.cache.prettifier_command_start_line.take(),
pane.cache.prettifier_command_text.take(),
)
} else {
(None, None)
};
if let Some(start) = start {
let output_start = start + 1;
if let Ok(term) = terminal.try_write() {
let lines = term.lines_text_range(output_start, *absolute_line);
crate::debug_info!(
"PRETTIFIER",
"submit_command_output: {} lines (rows {}..{})",
lines.len(),
output_start,
absolute_line
);
pipeline.submit_command_output(lines, cmd_text);
} else {
pipeline.on_command_end();
}
} else {
pipeline.on_command_end();
}
}
}
}
}
if shell_lifecycle_events.iter().any(|e| {
matches!(
e,
par_term_terminal::ShellLifecycleEvent::CommandFinished { .. }
)
}) {
self.play_alert_sound(crate::config::AlertEvent::CommandComplete);
}
if let Some(tab) = self.tab_manager.active_tab_mut() {
let needs_feed = tab.prettifier.as_ref().is_some_and(|p| p.is_enabled())
&& !is_alt_screen
&& (current_generation != tab.active_cache().prettifier_feed_generation
|| scroll_offset != tab.active_cache().prettifier_feed_scroll_offset);
if needs_feed
&& let Some(ref mut pipeline) = tab.prettifier
&& pipeline.detection_scope()
!= crate::prettifier::boundary::DetectionScope::CommandOutput
{
if let Some(ref mut pm) = tab.pane_manager
&& let Some(pane) = pm.focused_pane_mut()
{
pane.cache.prettifier_feed_generation = current_generation;
pane.cache.prettifier_feed_scroll_offset = scroll_offset;
}
let is_claude_session = {
let mut bridge = ClaudeCodePrettifierBridge {
pipeline,
pane_manager: &mut tab.pane_manager,
cells: &cells,
visible_lines,
grid_cols,
scrollback_len,
scroll_offset,
};
bridge.detect_session();
let active = bridge.pipeline.claude_code().is_active();
if active {
let viewport_hash = bridge.compute_viewport_hash();
let cached_hash = bridge.cached_viewport_hash();
let viewport_changed = viewport_hash != cached_hash;
if viewport_changed {
bridge.store_viewport_hash(viewport_hash);
}
bridge.segment_and_submit(viewport_changed);
}
active
};
if !is_claude_session {
pipeline.reset_boundary();
let feed_cols = if visible_lines > 0 && !cells.is_empty() {
cells.len() / visible_lines
} else {
grid_cols
};
let mut lines: Vec<(String, usize)> = Vec::with_capacity(visible_lines);
for row_idx in 0..visible_lines {
let absolute_row = scrollback_len.saturating_sub(scroll_offset) + row_idx;
let start = row_idx * feed_cols;
let end = (start + feed_cols).min(cells.len());
if start >= cells.len() {
break;
}
let line: String = cells[start..end]
.iter()
.map(|c| {
let g = c.grapheme.as_str();
if g.is_empty() || g == "\0" { " " } else { g }
})
.collect::<String>()
.trim_end()
.to_string();
lines.push((line, absolute_row));
}
if !lines.is_empty() {
let content_hash = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
for (line, row) in &lines {
line.hash(&mut hasher);
row.hash(&mut hasher);
}
hasher.finish()
};
let cached_last_hash = tab
.pane_manager
.as_ref()
.and_then(|pm| pm.focused_pane())
.map(|p| p.cache.prettifier_feed_last_hash)
.unwrap_or(0);
if content_hash == cached_last_hash {
crate::debug_trace!(
"PRETTIFIER",
"per-frame feed (non-CC): content unchanged, skipping"
);
} else {
let elapsed = tab
.pane_manager
.as_ref()
.and_then(|pm| pm.focused_pane())
.map(|p| p.cache.prettifier_feed_last_time.elapsed())
.unwrap_or_default();
let throttle = std::time::Duration::from_millis(150);
let has_block = !pipeline.active_blocks().is_empty();
if has_block && elapsed < throttle {
crate::debug_trace!(
"PRETTIFIER",
"per-frame feed (non-CC): throttled ({:.0}ms < {}ms), deferring",
elapsed.as_secs_f64() * 1000.0,
throttle.as_millis()
);
} else {
crate::debug_log!(
"PRETTIFIER",
"per-frame feed (non-CC): submitting {} visible lines as single block, scrollback={}, scroll_offset={}",
visible_lines,
scrollback_len,
scroll_offset
);
if let Some(ref mut pm) = tab.pane_manager
&& let Some(pane) = pm.focused_pane_mut()
{
pane.cache.prettifier_feed_last_hash = content_hash;
pane.cache.prettifier_feed_last_time =
std::time::Instant::now();
}
pipeline.submit_command_output(lines, None);
}
}
}
} } }
{
let block_count_after = self
.tab_manager
.active_tab()
.and_then(|t| t.prettifier.as_ref())
.map(|p| p.active_blocks().len())
.unwrap_or(0);
if block_count_after > prettifier_block_count_before {
crate::debug_info!(
"PRETTIFIER",
"new blocks detected ({} -> {}), invalidating cell cache",
prettifier_block_count_before,
block_count_after
);
self.invalidate_tab_cache();
}
}
let has_multiple_panes = self
.tab_manager
.active_tab()
.map(|t| t.has_multiple_panes())
.unwrap_or(false);
if let Some(tab) = self.tab_manager.active_tab_mut() {
if !has_multiple_panes {
tab.active_cache_mut().scrollback_len = scrollback_len;
let sb_len = tab.active_cache().scrollback_len;
tab.active_scroll_state_mut().clamp_to_scrollback(sb_len);
}
}
if self.copy_mode.active
&& let Ok(term) = terminal.try_write()
{
let (cols, rows) = term.dimensions();
self.copy_mode.update_dimensions(cols, rows, scrollback_len);
}
let (scrollback_marks, marks_override_scrollbar) = self.collect_scrollback_marks(&terminal);
if marks_override_scrollbar && scrollback_len > 0 && !has_multiple_panes {
show_scrollbar = true;
}
self.update_window_title_if_changed(&terminal_title, &cached_terminal_title, &hovered_url);
let total_lines = visible_lines + scrollback_len;
let debug_url_detect_time = self.apply_url_and_search_highlights(
&mut cells,
&renderer_size,
grid_cols,
scroll_offset,
scrollback_len,
visible_lines,
);
self.update_cursor_blink();
Some(FrameRenderData {
cells,
cursor_pos: current_cursor_pos,
cursor_style,
is_alt_screen,
scrollback_len,
show_scrollbar,
visible_lines,
grid_cols,
scrollback_marks,
total_lines,
debug_url_detect_time,
})
}
}