Class: Clacky::UI2::OutputBuffer
- Inherits:
-
Object
- Object
- Clacky::UI2::OutputBuffer
- Defined in:
- lib/clacky/ui2/output_buffer.rb
Overview
OutputBuffer manages the logical sequence of rendered output lines.
It replaces the scattered state that used to live across LayoutManager (@output_buffer + @output_row) and UIController (@progress_message / “last line” assumptions).
Core concepts:
-
Every append returns an
id. Callers can later replace(id, …) or remove(id) that exact entry without relying on “the last line”. -
Each entry tracks whether it has been “committed” to the terminal scrollback (i.e. scrolled off the top of the visible window by a native terminal n). Committed entries are NEVER re-drawn from the buffer again — this is what prevents the classic “scroll up shows a duplicated line” bug.
-
Entries may contain multi-line content (already wrapped). Each entry stores its visual line count so the renderer can compute exact rows to clear when replacing or removing.
The buffer itself does NOT talk to the terminal. It is a pure data structure; a renderer (LayoutManager) consumes it through the snapshot APIs: visible_entries, entry_by_id, tail_lines.
Defined Under Namespace
Classes: Entry
Constant Summary collapse
- DEFAULT_MAX_ENTRIES =
2000
Instance Attribute Summary collapse
-
#entries ⇒ Object
readonly
Returns the value of attribute entries.
Instance Method Summary collapse
-
#append(content, kind: :text) ⇒ Integer
Append a new entry.
-
#clear ⇒ Object
Clear everything.
-
#commit_oldest_lines(line_count) ⇒ Integer
Commit the oldest N entries.
-
#commit_through(id) ⇒ Object
Mark an entry (and every older live entry) as committed to terminal scrollback.
-
#entry_by_id(id) ⇒ Entry?
Look up an entry by id.
-
#initialize(max_entries: DEFAULT_MAX_ENTRIES) ⇒ OutputBuffer
constructor
A new instance of OutputBuffer.
-
#live?(id) ⇒ Boolean
Does this id still refer to a live, editable entry?.
-
#live_entries ⇒ Array<Entry>
Entries that are still live (not committed).
-
#live_line_count ⇒ Object
Total visual lines across live entries.
-
#live_size ⇒ Object
Number of live entries.
-
#remove(id) ⇒ Entry?
Remove an entry.
-
#replace(id, content) ⇒ Integer?
Replace an existing entry’s content.
-
#size ⇒ Object
Total number of entries (committed + live) currently tracked.
-
#tail_lines(n) ⇒ Array<String>
The last N *visual lines* across live entries, preserving entry boundaries.
-
#version ⇒ Object
Monotonic version (incremented on every mutation).
Constructor Details
#initialize(max_entries: DEFAULT_MAX_ENTRIES) ⇒ OutputBuffer
Returns a new instance of OutputBuffer.
51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/clacky/ui2/output_buffer.rb', line 51 def initialize(max_entries: DEFAULT_MAX_ENTRIES) @entries = [] # Array<Entry> in insertion order @index = {} # id => Entry (fast lookup) @next_id = 1 @max_entries = max_entries @mutex = Mutex.new # Monotonic counter incremented every time the buffer changes. # Renderers can compare this against a saved version to decide # whether their cached screen image is still valid. @version = 0 end |
Instance Attribute Details
#entries ⇒ Object (readonly)
Returns the value of attribute entries.
49 50 51 |
# File 'lib/clacky/ui2/output_buffer.rb', line 49 def entries @entries end |
Instance Method Details
#append(content, kind: :text) ⇒ Integer
Append a new entry. content may be a String (may include n) or an Array<String> of already-split lines.
69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/clacky/ui2/output_buffer.rb', line 69 def append(content, kind: :text) @mutex.synchronize do lines = normalize_lines(content) entry = Entry.new(id: next_id!, lines: lines, kind: kind, committed: false) @entries << entry @index[entry.id] = entry trim_if_needed bump_version entry.id end end |
#clear ⇒ Object
Clear everything. Used by /clear command.
251 252 253 254 255 256 257 |
# File 'lib/clacky/ui2/output_buffer.rb', line 251 def clear @mutex.synchronize do @entries.clear @index.clear bump_version end end |
#commit_oldest_lines(line_count) ⇒ Integer
Commit the oldest N entries. Used when the renderer scrolls N lines off the top via native n. It commits full entries greedily: if the N lines span across entry boundaries, all fully-scrolled entries are marked committed, and the partially-scrolled entry (if any) is left uncommitted (it will be handled next time).
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/clacky/ui2/output_buffer.rb', line 153 def commit_oldest_lines(line_count) return 0 if line_count <= 0 @mutex.synchronize do remaining = line_count committed = 0 @entries.each do |e| break if remaining <= 0 next if e.committed if e.height <= remaining e.committed = true remaining -= e.height committed += 1 else # Partial scroll — can't commit this entry yet break end end bump_version if committed > 0 committed end end |
#commit_through(id) ⇒ Object
Mark an entry (and every older live entry) as committed to terminal scrollback. Called by the renderer after it has emitted a native n that scrolled the top-of-screen row off into scrollback.
Committing always flows from oldest → newest: if entry X is committed, every entry older than X must also be committed, because they have already scrolled past X on the screen.
131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/clacky/ui2/output_buffer.rb', line 131 def commit_through(id) @mutex.synchronize do committed_any = false @entries.each do |e| break if e.id > id unless e.committed e.committed = true committed_any = true end end bump_version if committed_any end end |
#entry_by_id(id) ⇒ Entry?
Look up an entry by id.
217 218 219 |
# File 'lib/clacky/ui2/output_buffer.rb', line 217 def entry_by_id(id) @mutex.synchronize { @index[id] } end |
#live?(id) ⇒ Boolean
Does this id still refer to a live, editable entry?
223 224 225 226 227 228 |
# File 'lib/clacky/ui2/output_buffer.rb', line 223 def live?(id) @mutex.synchronize do e = @index[id] !!(e && !e.committed) end end |
#live_entries ⇒ Array<Entry>
Entries that are still live (not committed). These are candidates for re-rendering into the visible output area.
181 182 183 |
# File 'lib/clacky/ui2/output_buffer.rb', line 181 def live_entries @mutex.synchronize { @entries.reject(&:committed).dup } end |
#live_line_count ⇒ Object
Total visual lines across live entries.
241 242 243 |
# File 'lib/clacky/ui2/output_buffer.rb', line 241 def live_line_count @mutex.synchronize { @entries.sum { |e| e.committed ? 0 : e.height } } end |
#live_size ⇒ Object
Number of live entries.
236 237 238 |
# File 'lib/clacky/ui2/output_buffer.rb', line 236 def live_size @mutex.synchronize { @entries.count { |e| !e.committed } } end |
#remove(id) ⇒ Entry?
Remove an entry. Committed entries cannot be removed (they are in terminal scrollback). Returns the removed Entry, or nil if no-op.
109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/clacky/ui2/output_buffer.rb', line 109 def remove(id) @mutex.synchronize do entry = @index[id] return nil unless entry return nil if entry.committed @entries.delete(entry) @index.delete(id) bump_version entry end end |
#replace(id, content) ⇒ Integer?
Replace an existing entry’s content. If the id no longer exists (e.g. the entry was trimmed or already committed and recycled), this is a no-op and returns nil.
Replacing a committed entry is silently ignored — committed content lives in terminal scrollback and cannot be edited in place.
91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/clacky/ui2/output_buffer.rb', line 91 def replace(id, content) @mutex.synchronize do entry = @index[id] return nil unless entry return nil if entry.committed old_height = entry.lines.length entry.lines = normalize_lines(content) bump_version old_height end end |
#size ⇒ Object
Total number of entries (committed + live) currently tracked.
231 232 233 |
# File 'lib/clacky/ui2/output_buffer.rb', line 231 def size @mutex.synchronize { @entries.size } end |
#tail_lines(n) ⇒ Array<String>
The last N *visual lines* across live entries, preserving entry boundaries. Returns an Array<String> suitable for row-by-row painting. If the last live entry is taller than n, only its last n lines are returned.
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/clacky/ui2/output_buffer.rb', line 192 def tail_lines(n) return [] if n <= 0 @mutex.synchronize do collected = [] @entries.reverse_each do |e| break if collected.length >= n next if e.committed # Prepend the entry's lines in order remaining = n - collected.length if e.lines.length <= remaining collected = e.lines + collected else collected = e.lines.last(remaining) + collected break end end collected end end |
#version ⇒ Object
Monotonic version (incremented on every mutation).
246 247 248 |
# File 'lib/clacky/ui2/output_buffer.rb', line 246 def version @version end |