Implement diff-based rendering to eliminate screen flickering#61
Merged
Conversation
Add double-buffering terminal wrapper that renders to a pending buffer and only outputs changed cells on flush, eliminating the visual flicker caused by unconditional screen clears on every render cycle. New components: - TerminalCell: Value type representing a single cell with character, colors, and text decoration - FrameBuffer: 2D buffer of TerminalCell with efficient diffing via GetChangedRuns() that groups consecutive changes for minimal cursor movement - DiffingTerminal: IAnsiTerminal wrapper that intercepts all rendering calls, maintains current/pending frame buffers, and emits only changed cells Key behavior changes: - ClearScreen() now only clears the pending buffer, not the actual terminal - Flush() diffs pending vs current buffer and outputs only differences - ForceFullRefresh() forces complete redraw (used on resize or corruption) - TerminaApplication auto-wraps terminals with DiffingTerminal (except VirtualTerminal for testing) This is the same proven pattern used by ncurses, termbox, and other TUI libraries to achieve flicker-free rendering.
Remove SetCursorVisible calls from FlushFull and FlushDiff methods that were incorrectly toggling cursor visibility on every render. The application should control cursor visibility, not the terminal wrapper. This fixes a regression where multiple cursors would appear in the UI because the flush methods were unconditionally showing the cursor after each render cycle.
Skip resize when terminal reports zero width or height, which occurs in headless CI environments. This prevents ArgumentOutOfRangeException when FrameBuffer.Resize is called with invalid dimensions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DiffingTerminal) that renders to a pending buffer and only outputs changed cells on flushChanges
New components:
TerminalCell- Value type representing a single cell with character, colors, and text decorationFrameBuffer- 2D buffer with efficient diffing viaGetChangedRuns()that groups consecutive changes for minimal cursor movementDiffingTerminal-IAnsiTerminalwrapper that intercepts all rendering calls and emits only changed cellsKey behavior:
ClearScreen()now only clears the pending buffer, not the actual terminalFlush()diffs pending vs current buffer and outputs only differencesForceFullRefresh()forces complete redraw (used on resize)TerminaApplicationauto-wraps terminals withDiffingTerminalTest plan
FrameBuffer(14 tests)DiffingTerminal(21 tests)