Files
codex/codex-rs/tui/src/markdown_render/list_spacing.rs
T
Felipe Coury 4ca8e7afff Make transcript list spacing uniform after streaming (#46899)
## Why

Adding blank lines only after multiline items gives lists uneven spacing. Completed lists in redrawable transcript views can use consistent spacing based on their rendered width.

## What changed

- Keep assistant and plan lists compact while streaming in the fullscreen transcript, without holding back completed list rows.
- On completion or interruption, separate all sibling items if any item spans multiple rows; keep single-line lists compact and recompute spacing on resize.
- Preserve the reader's displayed content when consolidating streamed cells, and include spacing policy in the Markdown render cache key.
- Retain existing list spacing for native terminal scrollback.

## Testing

Add regression and snapshot coverage for streaming completion and interruption, width-dependent reflow, reader position, nested and quoted lists, item paragraphs, and preservation of links and source metadata.

GitOrigin-RevId: b2230aaf76ae798ebb6bf15563f8b8dcb6f3d19d
2026-09-20 22:55:19 +00:00

45 lines
1.5 KiB
Rust

//! List spacing is a layout policy, not a streaming boundary. Completed redrawable lists use
//! uniform sibling separators; streaming lists stay compact until source-backed consolidation.
use crate::terminal_hyperlinks::HyperlinkLine;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(crate) enum ListSpacing {
/// Preserve the historical spacing for terminal-owned scrollback.
#[default]
AfterMultiline,
/// Do not add separators between siblings while an owned transcript streams.
Compact,
/// Separate every sibling if any item occupies multiple rendered rows.
Uniform,
}
#[derive(Default)]
pub(super) struct UniformList {
pub(super) has_item: bool,
pub(super) multiline: bool,
pub(super) separators: Vec<usize>,
}
impl UniformList {
pub(super) fn finish(self, lines: &mut Vec<HyperlinkLine>) {
if self.multiline {
return;
}
let mut separators = self.separators.into_iter().peekable();
let Some(&start) = separators.peek() else {
return;
};
// Only remove this list's provisional separators, preserving source and link metadata.
let tail = lines.split_off(start);
lines.extend(tail.into_iter().enumerate().filter_map(|(offset, line)| {
if separators.peek() == Some(&(start + offset)) {
separators.next();
None
} else {
Some(line)
}
}));
}
}