A terminal text buffer — the core data structure that terminal emulators use to store and manipulate displayed text.
The implementation is split into five classes:
Color— a terminal color identified by a hex string. Provides 16 named ANSI constants and two defaults (foreground/background).Style— an enum of visual flags:BOLD,ITALIC,UNDERLINE.CellAttributes— an immutable value object grouping foreground color, background color, and a set of style flags. Acts as the "current pen" of the buffer.Cell— an immutable value object representing one grid position: a character plus the attributes active when it was written.Line— a mutable fixed-width array of cells representing one row in the grid.TerminalBuffer— the main class. Owns aLine[]screen and aDeque<Line>scrollback, and exposes all editing and content access operations.
Cell and CellAttributes are immutable.
Both are used as values, not entities. Making them immutable means they can be safely shared — Cell.EMPTY and CellAttributes.DEFAULT are singletons referenced by every blank cell in the grid without defensive copying. It also prevents a subtle bug where changing the current pen color would retroactively affect already-written cells if they held a mutable reference.
Line is mutable.
Lines are modified in place on every keystroke. Creating a new Line object per character would generate significant garbage in a hot path. Mutability is safe here because the buffer fully controls line ownership — lines in scrollback are never modified after being pushed off the screen (a copy is made via the copy constructor before adding to scrollback).
scrollUp() is the single growth point for scrollback.
All operations that cause lines to leave the screen — write() wrapping past the last row, insert() cascade overflow, and insertEmptyLine() — go through scrollUp(). This keeps the eviction and capacity logic in one place.
Logical row indexing across scrollback and screen.
Content access methods use a single logical row index where 0 is the oldest scrollback line and scrollbackSize + N is screen row N. This gives callers a uniform way to address the entire buffer history without needing to know which region a row lives in.
advanceCursor() is extracted as a private method.
Both write() and insert() share identical cursor advance logic. Extracting it removes the duplication and ensures both operations stay consistent if the wrapping behavior ever changes.
getLine() iterates the deque.
ArrayDeque does not support O(1) index access. Every call to getChar(), getAttrsAt(), or getLineText() for a scrollback row iterates from the front of the deque. For a scrollback of thousands of lines this becomes O(n) per access. A better structure would be an ArrayList with a start-index pointer to simulate a circular buffer — O(1) random access with O(1) amortized eviction from the front.
Color is modeled as a hex string.
The current Color class stores colors as RGB hex strings, which is intuitive but couples the model to a display format. A cleaner model would store the ANSI color index (0–15) as an integer and leave hex conversion to the rendering layer. This also makes equality checks slightly cheaper.
- Replace
ArrayDeque<Line>with anArrayList<Line>and a rolling start index for O(1) scrollback access. - Add wide character support with a
cellWidthfield onCelland corresponding handling inLine.insertCellAt()andTerminalBuffer.advanceCursor(). - Add
resize(int newWidth, int newHeight)with a chosen re-flow strategy (e.g. rewrap content to the new width). - Move tests into separate packages (
bufferfor unit tests ofLine,Celletc. andbuffer.integrationforTerminalBuffertests) to reflect the distinction between unit and integration-level tests. - Add 256-color and RGB color support by extending the
Colormodel with additional subclasses alongsideStandard.