Class: AsciinemaWin::ScreenBuffer
- Inherits:
-
Object
- Object
- AsciinemaWin::ScreenBuffer
- Defined in:
- lib/asciinema_win/screen_buffer.rb
Overview
Represents a captured screen buffer state with delta detection capabilities
This class captures the terminal screen buffer including:
-
Character content for each cell
-
Color/attribute information for each cell
-
Cursor position
-
Terminal dimensions
Defined Under Namespace
Classes: Cell
Instance Attribute Summary collapse
-
#attributes ⇒ Integer
readonly
Windows console attribute value (raw).
-
#background ⇒ Integer
readonly
Background color (ANSI color number 0-15 or 256-color/RGB).
-
#cells ⇒ Array<Array<Cell>>
readonly
2D array of cells [row].
-
#char ⇒ String
readonly
The character in this cell.
-
#cursor_x ⇒ Integer
readonly
Cursor X position (0-indexed).
-
#cursor_y ⇒ Integer
readonly
Cursor Y position (0-indexed).
-
#foreground ⇒ Integer
readonly
Foreground color (ANSI color number 0-15 or 256-color/RGB).
-
#height ⇒ Integer
readonly
Screen height in characters.
-
#timestamp ⇒ Float
readonly
Timestamp when this buffer was captured (monotonic clock).
-
#width ⇒ Integer
readonly
Screen width in characters.
Class Method Summary collapse
-
.capture ⇒ ScreenBuffer?
Capture the current screen buffer state from the Windows console.
-
.from_win32_data(data) ⇒ ScreenBuffer
Create a ScreenBuffer from Win32Screen capture data.
-
.parse_windows_attributes(attributes) ⇒ Array<Integer, Integer>
Parse Windows console attributes into foreground and background colors.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
(also: #eql?)
Check if this buffer equals another (content-wise).
-
#changed?(other) ⇒ Boolean
Check if buffer content has changed from another.
-
#diff(other = nil) ⇒ String
Compare this buffer with another and generate ANSI diff.
-
#get_cell(row, col) ⇒ Cell?
Get a cell at the given position.
-
#hash ⇒ Integer
Hash code for this buffer.
-
#initialize(width:, height:, cursor_x: 0, cursor_y: 0, timestamp: nil) ⇒ ScreenBuffer
constructor
Create a new ScreenBuffer with the given dimensions.
-
#set_cell(row, col, cell) ⇒ void
Set a cell at the given position.
-
#to_ansi ⇒ String
Convert entire buffer to ANSI escape sequence string.
-
#to_text ⇒ String
Convert buffer to plain text (no color codes).
Constructor Details
#initialize(width:, height:, cursor_x: 0, cursor_y: 0, timestamp: nil) ⇒ ScreenBuffer
Create a new ScreenBuffer with the given dimensions
78 79 80 81 82 83 84 85 |
# File 'lib/asciinema_win/screen_buffer.rb', line 78 def initialize(width:, height:, cursor_x: 0, cursor_y: 0, timestamp: nil) @width = width @height = height @cursor_x = cursor_x @cursor_y = cursor_y @timestamp = || Process.clock_gettime(Process::CLOCK_MONOTONIC) @cells = Array.new(height) { Array.new(width) { empty_cell } } end |
Instance Attribute Details
#attributes ⇒ Integer (readonly)
Returns Windows console attribute value (raw).
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/asciinema_win/screen_buffer.rb', line 30 Cell = Data.define(:char, :foreground, :background, :attributes) do # @return [Boolean] True if cell is empty (space with default colors) def empty? (char == " " || char == "\0") && foreground == 7 && background == 0 end # @return [Boolean] True if this cell equals another def ==(other) return false unless other.is_a?(Cell) char == other.char && foreground == other.foreground && background == other.background end alias eql? == # @return [Integer] Hash code for this cell def hash [char, foreground, background].hash end end |
#background ⇒ Integer (readonly)
Returns Background color (ANSI color number 0-15 or 256-color/RGB).
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/asciinema_win/screen_buffer.rb', line 30 Cell = Data.define(:char, :foreground, :background, :attributes) do # @return [Boolean] True if cell is empty (space with default colors) def empty? (char == " " || char == "\0") && foreground == 7 && background == 0 end # @return [Boolean] True if this cell equals another def ==(other) return false unless other.is_a?(Cell) char == other.char && foreground == other.foreground && background == other.background end alias eql? == # @return [Integer] Hash code for this cell def hash [char, foreground, background].hash end end |
#cells ⇒ Array<Array<Cell>> (readonly)
Returns 2D array of cells [row].
69 70 71 |
# File 'lib/asciinema_win/screen_buffer.rb', line 69 def cells @cells end |
#char ⇒ String (readonly)
Returns The character in this cell.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/asciinema_win/screen_buffer.rb', line 30 Cell = Data.define(:char, :foreground, :background, :attributes) do # @return [Boolean] True if cell is empty (space with default colors) def empty? (char == " " || char == "\0") && foreground == 7 && background == 0 end # @return [Boolean] True if this cell equals another def ==(other) return false unless other.is_a?(Cell) char == other.char && foreground == other.foreground && background == other.background end alias eql? == # @return [Integer] Hash code for this cell def hash [char, foreground, background].hash end end |
#cursor_x ⇒ Integer (readonly)
Returns Cursor X position (0-indexed).
60 61 62 |
# File 'lib/asciinema_win/screen_buffer.rb', line 60 def cursor_x @cursor_x end |
#cursor_y ⇒ Integer (readonly)
Returns Cursor Y position (0-indexed).
63 64 65 |
# File 'lib/asciinema_win/screen_buffer.rb', line 63 def cursor_y @cursor_y end |
#foreground ⇒ Integer (readonly)
Returns Foreground color (ANSI color number 0-15 or 256-color/RGB).
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/asciinema_win/screen_buffer.rb', line 30 Cell = Data.define(:char, :foreground, :background, :attributes) do # @return [Boolean] True if cell is empty (space with default colors) def empty? (char == " " || char == "\0") && foreground == 7 && background == 0 end # @return [Boolean] True if this cell equals another def ==(other) return false unless other.is_a?(Cell) char == other.char && foreground == other.foreground && background == other.background end alias eql? == # @return [Integer] Hash code for this cell def hash [char, foreground, background].hash end end |
#height ⇒ Integer (readonly)
Returns Screen height in characters.
57 58 59 |
# File 'lib/asciinema_win/screen_buffer.rb', line 57 def height @height end |
#timestamp ⇒ Float (readonly)
Returns Timestamp when this buffer was captured (monotonic clock).
66 67 68 |
# File 'lib/asciinema_win/screen_buffer.rb', line 66 def @timestamp end |
#width ⇒ Integer (readonly)
Returns Screen width in characters.
54 55 56 |
# File 'lib/asciinema_win/screen_buffer.rb', line 54 def width @width end |
Class Method Details
.capture ⇒ ScreenBuffer?
Capture the current screen buffer state from the Windows console
91 92 93 94 95 96 97 98 99 100 |
# File 'lib/asciinema_win/screen_buffer.rb', line 91 def self.capture unless Gem.win_platform? raise PlatformError, "Screen buffer capture requires Windows" end buffer_data = Win32Screen.capture return nil unless buffer_data from_win32_data(buffer_data) end |
.from_win32_data(data) ⇒ ScreenBuffer
Create a ScreenBuffer from Win32Screen capture data
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/asciinema_win/screen_buffer.rb', line 106 def self.from_win32_data(data) buffer = new( width: data[:width], height: data[:height], cursor_x: data[:cursor_x], cursor_y: data[:cursor_y] ) data[:lines].each_with_index do |line, row| chars = line[:chars] attributes = line[:attributes] chars.each_char.with_index do |char, col| break if col >= buffer.width attr = attributes[col] || 0 fg, bg = parse_windows_attributes(attr) buffer.set_cell(row, col, Cell.new( char: char, foreground: fg, background: bg, attributes: attr )) end end buffer end |
.parse_windows_attributes(attributes) ⇒ Array<Integer, Integer>
Parse Windows console attributes into foreground and background colors
140 141 142 143 144 145 146 147 148 |
# File 'lib/asciinema_win/screen_buffer.rb', line 140 def self.parse_windows_attributes(attributes) # Extract foreground (bits 0-3) fg = attributes & 0x0F # Extract background (bits 4-7) bg = (attributes >> 4) & 0x0F [fg, bg] end |
Instance Method Details
#==(other) ⇒ Boolean Also known as: eql?
Check if this buffer equals another (content-wise)
296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/asciinema_win/screen_buffer.rb', line 296 def ==(other) return false unless other.is_a?(ScreenBuffer) return false if @width != other.width || @height != other.height @cells.each_with_index do |row_cells, row| row_cells.each_with_index do |cell, col| return false unless cell == other.get_cell(row, col) end end true end |
#changed?(other) ⇒ Boolean
Check if buffer content has changed from another
320 321 322 |
# File 'lib/asciinema_win/screen_buffer.rb', line 320 def changed?(other) self != other end |
#diff(other = nil) ⇒ String
Compare this buffer with another and generate ANSI diff
Produces the minimal ANSI escape sequence needed to transform the other buffer’s display into this buffer’s display.
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/asciinema_win/screen_buffer.rb', line 180 def diff(other = nil) output = StringIO.new # If no previous buffer, render everything if other.nil? output << to_ansi return output.string end # Track what needs to be updated changes = [] @height.times do |row| @width.times do |col| current = get_cell(row, col) previous = other.get_cell(row, col) # Only emit changes if current != previous changes << { row: row, col: col, cell: current } end end end # If more than 50% changed, just redraw the whole screen total_cells = @width * @height if changes.length > total_cells / 2 output << "\e[H" # Move to home output << to_ansi return output.string end # Apply incremental changes last_row = -1 last_col = -1 last_fg = nil last_bg = nil changes.each do |change| row = change[:row] col = change[:col] cell = change[:cell] # Move cursor if needed if row != last_row || col != last_col + 1 output << "\e[#{row + 1};#{col + 1}H" end # Set colors if changed if cell.foreground != last_fg || cell.background != last_bg output << ansi_color_code(cell.foreground, cell.background) last_fg = cell.foreground last_bg = cell.background end output << cell.char last_row = row last_col = col end # Handle cursor position change if @cursor_x != other.cursor_x || @cursor_y != other.cursor_y output << "\e[#{@cursor_y + 1};#{@cursor_x + 1}H" end output.string end |
#get_cell(row, col) ⇒ Cell?
Get a cell at the given position
167 168 169 170 171 |
# File 'lib/asciinema_win/screen_buffer.rb', line 167 def get_cell(row, col) return nil if row < 0 || row >= @height || col < 0 || col >= @width @cells[row][col] end |
#hash ⇒ Integer
Returns Hash code for this buffer.
312 313 314 |
# File 'lib/asciinema_win/screen_buffer.rb', line 312 def hash [@width, @height, @cells].hash end |
#set_cell(row, col, cell) ⇒ void
This method returns an undefined value.
Set a cell at the given position
156 157 158 159 160 |
# File 'lib/asciinema_win/screen_buffer.rb', line 156 def set_cell(row, col, cell) return if row < 0 || row >= @height || col < 0 || col >= @width @cells[row][col] = cell end |
#to_ansi ⇒ String
Convert entire buffer to ANSI escape sequence string
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/asciinema_win/screen_buffer.rb', line 252 def to_ansi output = StringIO.new last_fg = nil last_bg = nil @cells.each_with_index do |row_cells, row| row_cells.each do |cell| # Set colors if changed if cell.foreground != last_fg || cell.background != last_bg output << "\e[0m" if last_fg || last_bg # Reset first output << ansi_color_code(cell.foreground, cell.background) last_fg = cell.foreground last_bg = cell.background end output << cell.char end # Newline between rows (except last) if row < @height - 1 output << "\e[0m" if last_fg || last_bg # Reset before newline output << "\r\n" last_fg = nil last_bg = nil end end # Final reset output << "\e[0m" output.string end |
#to_text ⇒ String
Convert buffer to plain text (no color codes)
288 289 290 |
# File 'lib/asciinema_win/screen_buffer.rb', line 288 def to_text @cells.map { |row| row.map(&:char).join.rstrip }.join("\n").rstrip end |