Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ tag does not describe the present program or its compatibility.

## Unreleased

### Fixed

- Heap-backed terminal cell symbols now share the 64 MiB terminal-buffer
accounting budget and over-budget frames are rejected before publication.

## 0.0.5 - 2026-08-11

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ from search input closes only with Esc or `F1`.
| Oversized graphemes | A grapheme over 1024 UTF-8 bytes is replaced safely |
| Opened file mutation | Fingerprint changes are reported as an error |
| Minimum terminal | 16 columns by 4 rows for the reader view |
| Terminal frames | Two cell buffers and heap symbols share a 64 MiB budget |
| Tested systems | Linux and macOS |

Windows is not supported. Other Unix systems may work, but are not currently
Expand Down
5 changes: 3 additions & 2 deletions docs/tut.1
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,9 @@ The complete reader requires a terminal of at least 16 columns by 4 rows.
Smaller terminals display resize and quit guidance.
.IP \(bu 2
Terminal geometry is limited to 524,288 cells.
The two fixed cell vectors are also subject to a 64 MiB accounting budget;
this is not a limit on total process memory or on heap-backed cell symbols.
The two fixed cell vectors and the heap-backed cell symbols in both frames
share a conservative 64 MiB accounting budget.
This is not a limit on total process memory.
.IP \(bu 2
Visible rendered content is limited to 32 MiB.
.IP \(bu 2
Expand Down
64 changes: 64 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,65 @@ impl RenderRows {
self.rows.as_ptr(),
)
}

#[cfg(test)]
pub(super) fn storage_bytes(&self) -> usize {
self.storage()
.bytes()
.expect("render-row storage capacities fit usize")
}

#[cfg(test)]
pub(super) fn from_prebuilt_runs(
run: &str,
count: usize,
cell_width: u16,
) -> Result<Self, TutError> {
let text_bytes = run
.len()
.checked_mul(count)
.ok_or(TutError::Allocation("test render rows"))?;
let mut text = String::new();
text.try_reserve_exact(text_bytes)
.map_err(|_| TutError::Allocation("test render rows"))?;
let mut spans = Vec::new();
spans
.try_reserve_exact(count)
.map_err(|_| TutError::Allocation("test render rows"))?;
let mut rows = Vec::new();
rows.try_reserve_exact(count)
.map_err(|_| TutError::Allocation("test render rows"))?;

for _ in 0..count {
let start = text.len();
text.push_str(run);
let end = text.len();
let span = spans.len();
spans.push(RenderSpan {
text: RenderTextRange::new(start, end),
source: GraphemeRange::new(
SourceOffset::from_usize(start),
SourceOffset::from_usize(end),
)
.expect("prebuilt test runs are nonempty"),
projection: RenderProjectionKind::Text,
cell_width: DisplayColumn::new(u32::from(cell_width)),
});
rows.push(RenderRowRange {
text: start..end,
spans: span..span + 1,
});
}

let rows = Self {
text,
spans,
rows,
reserve_attempts: RenderReserveAttempts::ZERO,
};
rows.storage().require(MAX_VISIBLE_RENDER_BYTES)?;
Ok(rows)
}
}

#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -490,6 +549,11 @@ impl<'a> RenderRowsView<'a> {
Self::new(&EMPTY_RENDER_ROWS, &[], None)
}

#[cfg(test)]
pub(super) const fn from_rows(rows: &'a RenderRows) -> Self {
Self::new(rows, &[], None)
}

pub(super) fn iter(self) -> impl ExactSizeIterator<Item = RenderRow<'a>> + 'a {
self.rows.iter()
}
Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ pub enum TutError {
VisibleRenderTooLarge {
limit: usize,
},
TerminalFrameSymbolBudgetExceeded {
limit: u64,
},
Layout(LayoutError),
Search(SearchError),
Log(LogError),
Expand Down Expand Up @@ -201,6 +204,9 @@ impl TutError {
Self::VisibleRenderTooLarge { limit } => {
format!("visible rendered content exceeds the {limit}-byte limit")
}
Self::TerminalFrameSymbolBudgetExceeded { limit } => {
format!("terminal frame symbols exceed the {limit}-byte limit")
}
Self::Layout(LayoutError::DocumentMismatch) => {
"layout state belongs to another document".to_owned()
}
Expand Down
Loading