perf(tui/history): cache render requests

This commit is contained in:
James Peter
2025-12-27 14:32:23 +10:00
parent 29a8e85456
commit 72b6650fe5
2 changed files with 408 additions and 111 deletions
+307 -109
View File
@@ -1571,6 +1571,14 @@ pub(crate) enum TurnOrigin {
Developer,
}
#[derive(Clone)]
struct RenderRequestSeed {
history_id: HistoryId,
use_cache: bool,
fallback_lines: Option<Rc<Vec<Line<'static>>>>,
kind: RenderRequestKind,
}
pub(crate) struct ChatWidget<'a> {
app_event_tx: AppEventSender,
code_op_tx: UnboundedSender<Op>,
@@ -1582,6 +1590,9 @@ pub(crate) struct ChatWidget<'a> {
history_cells: Vec<Box<dyn HistoryCell>>, // Store all history in memory
history_cell_ids: Vec<Option<HistoryId>>,
history_render: HistoryRenderState,
render_request_cache: RefCell<Vec<RenderRequestSeed>>,
render_request_cache_dirty: Cell<bool>,
history_prefix_append_only: Cell<bool>,
render_theme_epoch: u64,
history_state: HistoryState,
history_snapshot_dirty: bool,
@@ -4752,6 +4763,111 @@ impl ChatWidget<'_> {
/// Clear memoized cell heights (called when history/content changes)
fn invalidate_height_cache(&mut self) {
self.history_render.invalidate_height_cache();
self.mark_render_requests_dirty();
self.history_prefix_append_only.set(false);
}
fn mark_render_requests_dirty(&self) {
self.render_request_cache_dirty.set(true);
}
fn rebuild_render_request_cache(&self) {
let mut cache = self.render_request_cache.borrow_mut();
cache.clear();
cache.reserve(self.history_cells.len());
let reasoning_visible = self.is_reasoning_shown();
for (idx, cell) in self.history_cells.iter().enumerate() {
let (history_id, has_record) = if let Some(Some(id)) = self.history_cell_ids.get(idx) {
let exists = self.history_state.index_of(*id).is_some();
(*id, exists)
} else {
(HistoryId::ZERO, false)
};
let cell_has_custom_render = cell.has_custom_render();
let is_streaming = cell
.as_any()
.downcast_ref::<crate::history_cell::StreamingContentCell>()
.is_some();
let mut use_cache = history_id != HistoryId::ZERO
&& has_record
&& !cell_has_custom_render
&& !cell.is_animating()
&& !is_streaming;
let mut kind = RenderRequestKind::Legacy;
if history_id != HistoryId::ZERO {
if let Some(record) = self.history_state.record(history_id) {
match record {
HistoryRecord::Exec(_) => {
kind = RenderRequestKind::Exec { id: history_id };
}
HistoryRecord::MergedExec(_) => {
kind = RenderRequestKind::MergedExec { id: history_id };
}
HistoryRecord::Explore(_) => {
let hold_header = self.rendered_explore_should_hold(idx);
kind = RenderRequestKind::Explore {
id: history_id,
hold_header,
full_detail: reasoning_visible,
};
}
HistoryRecord::Diff(_) => {
kind = RenderRequestKind::Diff { id: history_id };
}
HistoryRecord::AssistantStream(stream_state) => {
kind = RenderRequestKind::Streaming { id: history_id };
if stream_state.in_progress {
use_cache = false;
}
}
HistoryRecord::AssistantMessage(_) => {
kind = RenderRequestKind::Assistant { id: history_id };
}
_ => {}
}
}
}
let mut fallback_lines: Option<Rc<Vec<Line<'static>>>> = None;
if !cell_has_custom_render && !is_streaming {
if history_id != HistoryId::ZERO {
if let Some(record) = self.history_state.record(history_id) {
if let Some(lines) = self.fallback_lines_for_record(cell.as_ref(), record) {
let cached = self
.history_render
.cached_fallback_lines(history_id, || lines);
fallback_lines = Some(cached);
}
}
} else {
let lines = cell.display_lines_trimmed();
if !lines.is_empty() {
fallback_lines = Some(Rc::new(lines));
}
}
}
cache.push(RenderRequestSeed {
history_id,
use_cache,
fallback_lines,
kind,
});
}
self.render_request_cache_dirty.set(false);
}
fn ensure_render_request_cache(&self) {
if self.render_request_cache_dirty.get()
|| self.render_request_cache.borrow().len() != self.history_cells.len()
{
self.rebuild_render_request_cache();
}
}
fn fallback_lines_for_record(
@@ -5976,6 +6092,9 @@ impl ChatWidget<'_> {
background_review_guard: None,
processed_auto_review_agents: HashSet::new(),
pending_turn_descriptor: None,
render_request_cache: RefCell::new(Vec::new()),
render_request_cache_dirty: Cell::new(true),
history_prefix_append_only: Cell::new(true),
pending_auto_turn_config: None,
overall_task_status: "preparing".to_string(),
active_plan_title: None,
@@ -6333,6 +6452,9 @@ impl ChatWidget<'_> {
background_review_guard: None,
processed_auto_review_agents: HashSet::new(),
pending_turn_descriptor: None,
render_request_cache: RefCell::new(Vec::new()),
render_request_cache_dirty: Cell::new(true),
history_prefix_append_only: Cell::new(true),
pending_auto_turn_config: None,
overall_task_status: "preparing".to_string(),
active_plan_title: None,
@@ -7092,6 +7214,76 @@ impl ChatWidget<'_> {
total_height.saturating_sub(content_area_height)
}
fn try_append_prefix_fast(
&self,
render_requests: &[RenderRequest<'_>],
render_settings: RenderSettings,
prefix_width: u16,
) -> bool {
if !self.history_prefix_append_only.get() {
return false;
}
if !self
.history_render
.can_append_prefix(prefix_width, render_requests.len())
{
return false;
}
let prev_count = self.history_render.last_prefix_count();
if prev_count == 0 || render_requests.len() != prev_count.saturating_add(1) {
return false;
}
let history_count = self.history_cells.len();
if history_count < 2 {
return false;
}
if history_count != self
.history_render
.last_history_count()
.saturating_add(1)
{
return false;
}
if render_requests.len() != history_count {
return false;
}
let history_tail_start = history_count - 2;
let tail = &render_requests[history_tail_start..history_count];
if tail.len() != 2 {
return false;
}
let cells = self
.history_render
.visible_cells(&self.history_state, tail, render_settings);
if cells.len() != 2 {
return false;
}
let prev = &cells[0];
let next = &cells[1];
if prev.height == 0 || next.height == 0 {
return false;
}
let prev_is_reasoning = prev
.cell
.and_then(|cell| cell.as_any().downcast_ref::<crate::history_cell::CollapsibleReasoningCell>())
.is_some();
let next_is_reasoning = next
.cell
.and_then(|cell| cell.as_any().downcast_ref::<crate::history_cell::CollapsibleReasoningCell>())
.is_some();
if prev_is_reasoning || next_is_reasoning {
return false;
}
let spacing = 1u16;
let spacing_range = self
.history_render
.extend_prefix_for_append(prefix_width, spacing, next.height, history_count);
if let Some(range) = spacing_range {
self.history_render.append_spacing_range(range);
}
true
}
pub(crate) fn handle_key_event(&mut self, key_event: KeyEvent) {
if settings_handlers::handle_settings_key(self, key_event) {
return;
@@ -8918,6 +9110,9 @@ impl ChatWidget<'_> {
}
let append = pos == self.history_cells.len();
if !append {
self.history_prefix_append_only.set(false);
}
if append {
self.history_cells.push(cell);
self.history_cell_ids.push(maybe_id);
@@ -8987,7 +9182,16 @@ impl ChatWidget<'_> {
} else {
self.cell_order_dbg.insert(pos, Some(dbg));
}
self.invalidate_height_cache();
if let Some(id) = maybe_id {
if id != HistoryId::ZERO {
self.history_render.invalidate_history_id(id);
} else {
self.history_render.invalidate_prefix_only();
}
} else {
self.history_render.invalidate_prefix_only();
}
self.mark_render_requests_dirty();
self.autoscroll_if_near_bottom();
self.bottom_pane.set_has_chat_history(true);
self.process_animation_cleanup();
@@ -9118,6 +9322,9 @@ impl ChatWidget<'_> {
Self::assign_history_id_inner(&mut cell, id);
let append = pos == self.history_cells.len();
if !append {
self.history_prefix_append_only.set(false);
}
if append {
self.history_cells.push(cell);
self.history_cell_ids.push(Some(id));
@@ -9184,7 +9391,8 @@ impl ChatWidget<'_> {
} else {
self.cell_order_dbg.insert(pos, Some(dbg));
}
self.invalidate_height_cache();
self.history_render.invalidate_history_id(id);
self.mark_render_requests_dirty();
self.autoscroll_if_near_bottom();
self.bottom_pane.set_has_chat_history(true);
self.process_animation_cleanup();
@@ -9194,7 +9402,6 @@ impl ChatWidget<'_> {
self.app_event_tx.send(AppEvent::RequestRedraw);
self.refresh_explore_trailing_flags();
self.refresh_reasoning_collapsed_visibility();
self.history_render.invalidate_history_id(id);
self.mark_history_dirty();
pos
}
@@ -9854,6 +10061,7 @@ impl ChatWidget<'_> {
return;
}
let old_id = self.history_cell_ids.get(idx).and_then(|id| *id);
let record = history_cell::record_from_cell(cell.as_ref());
let mut maybe_id = None;
@@ -9893,7 +10101,18 @@ impl ChatWidget<'_> {
if idx < self.history_cell_ids.len() {
self.history_cell_ids[idx] = maybe_id;
}
self.invalidate_height_cache();
if let Some(id) = old_id {
self.history_render.invalidate_history_id(id);
} else {
self.history_render.invalidate_prefix_only();
}
if let Some(id) = maybe_id {
if Some(id) != old_id {
self.history_render.invalidate_history_id(id);
}
}
self.mark_render_requests_dirty();
self.history_prefix_append_only.set(false);
self.request_redraw();
self.refresh_explore_trailing_flags();
// Keep debug info for this cell index as-is.
@@ -9905,6 +10124,7 @@ impl ChatWidget<'_> {
return;
}
let removed_id = self.history_cell_ids.get(idx).and_then(|id| *id);
if let Some(record_idx) = self.record_index_for_cell(idx) {
let _ = self
.history_state
@@ -9921,7 +10141,13 @@ impl ChatWidget<'_> {
if idx < self.cell_order_dbg.len() {
self.cell_order_dbg.remove(idx);
}
self.invalidate_height_cache();
if let Some(id) = removed_id {
self.history_render.invalidate_history_id(id);
} else {
self.history_render.invalidate_prefix_only();
}
self.mark_render_requests_dirty();
self.history_prefix_append_only.set(false);
self.request_redraw();
self.refresh_explore_trailing_flags();
self.mark_history_dirty();
@@ -11233,6 +11459,7 @@ impl ChatWidget<'_> {
fn mark_history_dirty(&mut self) {
self.history_snapshot_dirty = true;
self.render_request_cache_dirty.set(true);
self.flush_history_snapshot_if_needed(false);
}
@@ -11804,6 +12031,8 @@ impl ChatWidget<'_> {
self.history_state.restore(snapshot);
self.history_render.invalidate_all();
self.mark_render_requests_dirty();
self.history_prefix_append_only.set(false);
self.history_cells.clear();
self.history_cell_ids.clear();
@@ -17052,6 +17281,8 @@ fi\n\
pub(crate) fn show_auto_drive_settings(&mut self) {
self.history_render.invalidate_all();
self.mark_render_requests_dirty();
self.history_prefix_append_only.set(false);
self.ensure_auto_drive_settings_overlay();
}
@@ -17066,6 +17297,8 @@ fi\n\
self.close_settings_overlay();
}
self.history_render.invalidate_all();
self.mark_render_requests_dirty();
self.history_prefix_append_only.set(false);
let should_rebuild_view = if self.auto_state.is_active() {
!self.auto_state.is_paused_manual()
} else {
@@ -23462,6 +23695,8 @@ Have we met every part of this goal and is there no further work to do?"#
self.last_theme = new;
self.render_theme_epoch = self.render_theme_epoch.saturating_add(1);
self.history_render.invalidate_all();
self.mark_render_requests_dirty();
self.history_prefix_append_only.set(false);
self.app_event_tx.send(AppEvent::RequestRedraw);
}
@@ -36586,20 +36821,6 @@ impl WidgetRef for &ChatWidget<'_> {
.fg(crate::colors::text());
fill_rect(buf, history_area, Some(' '), base_style);
// Collect all content items into a single list.
// The shared renderer cache (documented in docs/history_render_cache_bridge.md)
// consumes this list via `RenderRequest` so we can reuse per-cell caches
// while progressively moving the draw loop toward pure HistoryRecord state.
let mut all_content: Vec<&dyn HistoryCell> = Vec::new();
for cell in self.history_cells.iter() {
all_content.push(cell);
}
// Add active/streaming cell if present
if let Some(ref cell) = self.active_exec_cell {
all_content.push(cell as &dyn HistoryCell);
}
// Add live streaming content if present
let streaming_lines = self
.live_builder
@@ -36616,10 +36837,6 @@ impl WidgetRef for &ChatWidget<'_> {
None
};
if let Some(ref cell) = streaming_cell {
all_content.push(cell);
}
// Append any queued user messages as sticky preview cells at the very
// end so they always render at the bottom until they are dispatched.
let mut queued_preview_cells: Vec<crate::history_cell::PlainHistoryCell> = Vec::new();
@@ -36628,101 +36845,66 @@ impl WidgetRef for &ChatWidget<'_> {
let state = history_cell::new_queued_user_prompt(qm.display_text.clone());
queued_preview_cells.push(crate::history_cell::PlainHistoryCell::from_state(state));
}
for c in &queued_preview_cells {
all_content.push(c as &dyn HistoryCell);
}
}
let mut render_requests: Vec<RenderRequest> = Vec::with_capacity(all_content.len());
for (idx, cell) in all_content.iter().enumerate() {
let (history_id, has_record) = if idx < self.history_cell_ids.len() {
if let Some(id) = self.history_cell_ids[idx] {
let exists = self.history_state.index_of(id).is_some();
(id, exists)
} else {
(HistoryId::ZERO, false)
}
} else {
(HistoryId::ZERO, false)
};
let cell_has_custom_render = cell.has_custom_render();
let is_streaming = cell
.as_any()
.downcast_ref::<crate::history_cell::StreamingContentCell>()
.is_some();
let mut cacheable = history_id != HistoryId::ZERO
&& has_record
&& !cell_has_custom_render
&& !cell.is_animating()
&& !is_streaming;
self.ensure_render_request_cache();
let render_request_cache = self.render_request_cache.borrow();
let mut render_requests: Vec<RenderRequest> =
Vec::with_capacity(self.history_cells.len().saturating_add(3));
for (cell, seed) in self
.history_cells
.iter()
.zip(render_request_cache.iter())
{
let assistant = cell
.as_any()
.downcast_ref::<crate::history_cell::AssistantMarkdownCell>();
let mut fallback_lines = None;
let mut kind = RenderRequestKind::Legacy;
if history_id != HistoryId::ZERO {
if let Some(record) = self.history_state.record(history_id) {
match record {
HistoryRecord::Exec(_) => {
kind = RenderRequestKind::Exec { id: history_id };
}
HistoryRecord::MergedExec(_) => {
kind = RenderRequestKind::MergedExec { id: history_id };
}
HistoryRecord::Explore(_) => {
let hold_header = if idx < self.history_cells.len() {
self.rendered_explore_should_hold(idx)
} else {
true
};
let full_detail = self.is_reasoning_shown();
kind = RenderRequestKind::Explore {
id: history_id,
hold_header,
full_detail,
};
}
HistoryRecord::Diff(_) => {
kind = RenderRequestKind::Diff { id: history_id };
}
HistoryRecord::AssistantStream(stream_state) => {
kind = RenderRequestKind::Streaming { id: history_id };
if stream_state.in_progress {
cacheable = false;
}
}
HistoryRecord::AssistantMessage(_) => {
kind = RenderRequestKind::Assistant { id: history_id };
}
other => {
fallback_lines =
self.fallback_lines_for_record(*cell, other);
}
}
}
}
if fallback_lines.is_none() && !cell_has_custom_render && history_id == HistoryId::ZERO {
let lines = cell.display_lines_trimmed();
if !lines.is_empty() {
fallback_lines = Some(lines);
}
}
render_requests.push(RenderRequest {
history_id,
cell: Some(*cell),
history_id: seed.history_id,
cell: Some(cell.as_ref()),
assistant,
use_cache: cacheable,
fallback_lines,
kind,
use_cache: seed.use_cache,
fallback_lines: seed.fallback_lines.clone(),
kind: seed.kind.clone(),
config: &self.config,
});
}
if let Some(ref cell) = self.active_exec_cell {
render_requests.push(RenderRequest {
history_id: HistoryId::ZERO,
cell: Some(cell as &dyn HistoryCell),
assistant: None,
use_cache: false,
fallback_lines: None,
kind: RenderRequestKind::Legacy,
config: &self.config,
});
}
if let Some(ref cell) = streaming_cell {
render_requests.push(RenderRequest {
history_id: HistoryId::ZERO,
cell: Some(cell as &dyn HistoryCell),
assistant: None,
use_cache: false,
fallback_lines: None,
kind: RenderRequestKind::Legacy,
config: &self.config,
});
}
for c in &queued_preview_cells {
render_requests.push(RenderRequest {
history_id: HistoryId::ZERO,
cell: Some(c as &dyn HistoryCell),
assistant: None,
use_cache: false,
fallback_lines: None,
kind: RenderRequestKind::Legacy,
config: &self.config,
});
}
// Calculate total content height using prefix sums; build if needed
@@ -36749,6 +36931,15 @@ impl WidgetRef for &ChatWidget<'_> {
let perf_enabled = self.perf_state.enabled;
let mut rendered_cells_full: Option<Vec<VisibleCell>> = None;
if needs_prefix_rebuild {
let mut used_fast_append = false;
if self.try_append_prefix_fast(&render_requests, render_settings, content_area.width) {
used_fast_append = true;
self.history_prefix_append_only.set(true);
}
if used_fast_append {
// Prefix sums already updated; skip the full rebuild path.
rendered_cells_full = None;
} else {
if perf_enabled {
let mut p = self.perf_state.stats.borrow_mut();
p.prefix_rebuilds = p.prefix_rebuilds.saturating_add(1);
@@ -36835,10 +37026,17 @@ impl WidgetRef for &ChatWidget<'_> {
let mut p = self.perf_state.stats.borrow_mut();
p.ns_total_height = p.ns_total_height.saturating_add(elapsed);
}
self.history_render
.update_prefix_cache(content_area.width, prefix, total_height, cells.len());
self.history_render.update_prefix_cache(
content_area.width,
prefix,
total_height,
cells.len(),
self.history_cells.len(),
);
self.history_render.update_spacing_ranges(spacing_ranges);
rendered_cells_full = Some(cells);
self.history_prefix_append_only.set(true);
}
}
let mut total_height = self.history_render.last_total_height();
+101 -2
View File
@@ -32,11 +32,13 @@ use unicode_width::UnicodeWidthStr;
pub(crate) struct HistoryRenderState {
pub(crate) layout_cache: RefCell<HashMap<CacheKey, Rc<CachedLayout>>>,
pub(crate) height_cache: RefCell<HashMap<CacheKey, u16>>,
fallback_cache: RefCell<HashMap<HistoryId, Rc<Vec<Line<'static>>>>>,
pub(crate) height_cache_last_width: Cell<u16>,
pub(crate) prefix_sums: RefCell<Vec<u16>>,
pub(crate) last_prefix_width: Cell<u16>,
pub(crate) last_prefix_count: Cell<usize>,
pub(crate) last_total_height: Cell<u16>,
pub(crate) last_history_count: Cell<usize>,
pub(crate) prefix_valid: Cell<bool>,
// Row intervals that correspond to inter-cell spacing so we can avoid
// landing the viewport on empty gaps when scrolling.
@@ -51,11 +53,13 @@ impl HistoryRenderState {
Self {
layout_cache: RefCell::new(HashMap::new()),
height_cache: RefCell::new(HashMap::new()),
fallback_cache: RefCell::new(HashMap::new()),
height_cache_last_width: Cell::new(0),
prefix_sums: RefCell::new(Vec::new()),
last_prefix_width: Cell::new(0),
last_prefix_count: Cell::new(0),
last_total_height: Cell::new(0),
last_history_count: Cell::new(0),
prefix_valid: Cell::new(false),
spacing_ranges: RefCell::new(Vec::new()),
bottom_spacer_range: Cell::new(None),
@@ -67,8 +71,10 @@ impl HistoryRenderState {
pub(crate) fn invalidate_height_cache(&self) {
self.layout_cache.borrow_mut().clear();
self.height_cache.borrow_mut().clear();
self.fallback_cache.borrow_mut().clear();
self.prefix_sums.borrow_mut().clear();
self.last_total_height.set(0);
self.last_history_count.set(0);
self.prefix_valid.set(false);
self.spacing_ranges.borrow_mut().clear();
self.bottom_spacer_range.set(None);
@@ -84,8 +90,10 @@ impl HistoryRenderState {
self.height_cache
.borrow_mut()
.retain(|key, _| key.width == width);
self.fallback_cache.borrow_mut().clear();
self.prefix_sums.borrow_mut().clear();
self.last_total_height.set(0);
self.last_history_count.set(0);
self.prefix_valid.set(false);
self.height_cache_last_width.set(width);
self.spacing_ranges.borrow_mut().clear();
@@ -105,8 +113,10 @@ impl HistoryRenderState {
self.height_cache
.borrow_mut()
.retain(|key, _| key.history_id != id);
self.fallback_cache.borrow_mut().remove(&id);
self.prefix_sums.borrow_mut().clear();
self.last_total_height.set(0);
self.last_history_count.set(0);
self.prefix_valid.set(false);
self.spacing_ranges.borrow_mut().clear();
self.bottom_spacer_range.set(None);
@@ -117,8 +127,21 @@ impl HistoryRenderState {
pub(crate) fn invalidate_all(&self) {
self.layout_cache.borrow_mut().clear();
self.height_cache.borrow_mut().clear();
self.fallback_cache.borrow_mut().clear();
self.prefix_sums.borrow_mut().clear();
self.last_total_height.set(0);
self.last_history_count.set(0);
self.prefix_valid.set(false);
self.spacing_ranges.borrow_mut().clear();
self.bottom_spacer_range.set(None);
self.bottom_spacer_lines.set(0);
self.pending_bottom_spacer_lines.set(None);
}
pub(crate) fn invalidate_prefix_only(&self) {
self.prefix_sums.borrow_mut().clear();
self.last_total_height.set(0);
self.last_history_count.set(0);
self.prefix_valid.set(false);
self.spacing_ranges.borrow_mut().clear();
self.bottom_spacer_range.set(None);
@@ -145,6 +168,7 @@ impl HistoryRenderState {
prefix: Vec<u16>,
total_height: u16,
count: usize,
history_count: usize,
) {
{
let mut ps = self.prefix_sums.borrow_mut();
@@ -153,9 +177,27 @@ impl HistoryRenderState {
self.last_prefix_width.set(width);
self.last_prefix_count.set(count);
self.last_total_height.set(total_height);
self.last_history_count.set(history_count);
self.prefix_valid.set(true);
}
pub(crate) fn cached_fallback_lines<F>(&self, history_id: HistoryId, build: F) -> Rc<Vec<Line<'static>>>
where
F: FnOnce() -> Vec<Line<'static>>,
{
if history_id == HistoryId::ZERO {
return Rc::new(build());
}
if let Some(lines) = self.fallback_cache.borrow().get(&history_id) {
return Rc::clone(lines);
}
let lines = Rc::new(build());
self.fallback_cache
.borrow_mut()
.insert(history_id, Rc::clone(&lines));
lines
}
pub(crate) fn update_spacing_ranges(&self, ranges: Vec<(u16, u16)>) {
*self.spacing_ranges.borrow_mut() = ranges;
}
@@ -239,6 +281,63 @@ impl HistoryRenderState {
self.last_total_height.get()
}
pub(crate) fn last_prefix_count(&self) -> usize {
self.last_prefix_count.get()
}
pub(crate) fn last_history_count(&self) -> usize {
self.last_history_count.get()
}
pub(crate) fn can_append_prefix(&self, width: u16, count: usize) -> bool {
self.prefix_valid.get()
&& self.last_prefix_width.get() == width
&& count == self.last_prefix_count.get().saturating_add(1)
}
pub(crate) fn extend_prefix_for_append(
&self,
width: u16,
spacing: u16,
new_height: u16,
new_history_count: usize,
) -> Option<(u16, u16)> {
if !self.prefix_valid.get() || self.last_prefix_width.get() != width {
return None;
}
let prev_count = self.last_prefix_count.get();
if prev_count == 0 {
return None;
}
if new_history_count != self.last_history_count.get().saturating_add(1) {
return None;
}
let mut ps = self.prefix_sums.borrow_mut();
if ps.len() != prev_count.saturating_add(1) {
return None;
}
let old_total = *ps.last().unwrap_or(&0);
let spacing_start = old_total;
let spacing_end = spacing_start.saturating_add(spacing);
if let Some(last) = ps.last_mut() {
*last = spacing_end;
} else {
return None;
}
let new_total = spacing_end.saturating_add(new_height);
ps.push(new_total);
self.last_total_height.set(new_total);
self.last_prefix_count.set(prev_count.saturating_add(1));
self.last_history_count.set(new_history_count);
self.last_prefix_width.set(width);
self.prefix_valid.set(true);
if spacing > 0 { Some((spacing_start, spacing_end)) } else { None }
}
pub(crate) fn append_spacing_range(&self, range: (u16, u16)) {
self.spacing_ranges.borrow_mut().push(range);
}
pub(crate) fn visible_cells<'a>(
&self,
history_state: &HistoryState,
@@ -557,7 +656,7 @@ pub(crate) struct RenderRequest<'a> {
pub cell: Option<&'a dyn HistoryCell>,
pub assistant: Option<&'a AssistantMarkdownCell>,
pub use_cache: bool,
pub fallback_lines: Option<Vec<Line<'static>>>,
pub fallback_lines: Option<Rc<Vec<Line<'static>>>>,
pub kind: RenderRequestKind,
pub config: &'a Config,
}
@@ -616,7 +715,7 @@ impl<'a> RenderRequest<'a> {
}
if let Some(lines) = &self.fallback_lines {
return lines.clone();
return lines.as_ref().clone();
}
Vec::new()
}