asciinema_win 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +575 -0
- data/exe/asciinema_win +17 -0
- data/lib/asciinema_win/ansi_parser.rb +437 -0
- data/lib/asciinema_win/asciicast.rb +537 -0
- data/lib/asciinema_win/cli.rb +591 -0
- data/lib/asciinema_win/export.rb +780 -0
- data/lib/asciinema_win/output_organizer.rb +276 -0
- data/lib/asciinema_win/player.rb +348 -0
- data/lib/asciinema_win/recorder.rb +480 -0
- data/lib/asciinema_win/screen_buffer.rb +375 -0
- data/lib/asciinema_win/themes.rb +334 -0
- data/lib/asciinema_win/version.rb +6 -0
- data/lib/asciinema_win.rb +153 -0
- data/lib/rich/_palettes.rb +148 -0
- data/lib/rich/box.rb +342 -0
- data/lib/rich/cells.rb +512 -0
- data/lib/rich/color.rb +628 -0
- data/lib/rich/color_triplet.rb +220 -0
- data/lib/rich/console.rb +549 -0
- data/lib/rich/control.rb +332 -0
- data/lib/rich/json.rb +254 -0
- data/lib/rich/layout.rb +314 -0
- data/lib/rich/markdown.rb +509 -0
- data/lib/rich/markup.rb +175 -0
- data/lib/rich/panel.rb +311 -0
- data/lib/rich/progress.rb +430 -0
- data/lib/rich/segment.rb +387 -0
- data/lib/rich/style.rb +433 -0
- data/lib/rich/syntax.rb +1145 -0
- data/lib/rich/table.rb +525 -0
- data/lib/rich/terminal_theme.rb +126 -0
- data/lib/rich/text.rb +433 -0
- data/lib/rich/tree.rb +220 -0
- data/lib/rich/version.rb +5 -0
- data/lib/rich/win32_console.rb +859 -0
- data/lib/rich.rb +108 -0
- metadata +123 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
module AsciinemaWin
|
|
6
|
+
# Represents a captured screen buffer state with delta detection capabilities
|
|
7
|
+
#
|
|
8
|
+
# This class captures the terminal screen buffer including:
|
|
9
|
+
# - Character content for each cell
|
|
10
|
+
# - Color/attribute information for each cell
|
|
11
|
+
# - Cursor position
|
|
12
|
+
# - Terminal dimensions
|
|
13
|
+
#
|
|
14
|
+
# @example Capture and compare screen states
|
|
15
|
+
# buffer1 = ScreenBuffer.capture
|
|
16
|
+
# # ... terminal content changes ...
|
|
17
|
+
# buffer2 = ScreenBuffer.capture
|
|
18
|
+
# diff = buffer2.diff(buffer1)
|
|
19
|
+
# puts diff # ANSI escape sequences to transform buffer1 to buffer2
|
|
20
|
+
class ScreenBuffer
|
|
21
|
+
# Cell data structure representing a single character cell
|
|
22
|
+
# @!attribute [r] char
|
|
23
|
+
# @return [String] The character in this cell
|
|
24
|
+
# @!attribute [r] foreground
|
|
25
|
+
# @return [Integer] Foreground color (ANSI color number 0-15 or 256-color/RGB)
|
|
26
|
+
# @!attribute [r] background
|
|
27
|
+
# @return [Integer] Background color (ANSI color number 0-15 or 256-color/RGB)
|
|
28
|
+
# @!attribute [r] attributes
|
|
29
|
+
# @return [Integer] Windows console attribute value (raw)
|
|
30
|
+
Cell = Data.define(:char, :foreground, :background, :attributes) do
|
|
31
|
+
# @return [Boolean] True if cell is empty (space with default colors)
|
|
32
|
+
def empty?
|
|
33
|
+
(char == " " || char == "\0") && foreground == 7 && background == 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Boolean] True if this cell equals another
|
|
37
|
+
def ==(other)
|
|
38
|
+
return false unless other.is_a?(Cell)
|
|
39
|
+
|
|
40
|
+
char == other.char &&
|
|
41
|
+
foreground == other.foreground &&
|
|
42
|
+
background == other.background
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
alias eql? ==
|
|
46
|
+
|
|
47
|
+
# @return [Integer] Hash code for this cell
|
|
48
|
+
def hash
|
|
49
|
+
[char, foreground, background].hash
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [Integer] Screen width in characters
|
|
54
|
+
attr_reader :width
|
|
55
|
+
|
|
56
|
+
# @return [Integer] Screen height in characters
|
|
57
|
+
attr_reader :height
|
|
58
|
+
|
|
59
|
+
# @return [Integer] Cursor X position (0-indexed)
|
|
60
|
+
attr_reader :cursor_x
|
|
61
|
+
|
|
62
|
+
# @return [Integer] Cursor Y position (0-indexed)
|
|
63
|
+
attr_reader :cursor_y
|
|
64
|
+
|
|
65
|
+
# @return [Float] Timestamp when this buffer was captured (monotonic clock)
|
|
66
|
+
attr_reader :timestamp
|
|
67
|
+
|
|
68
|
+
# @return [Array<Array<Cell>>] 2D array of cells [row][col]
|
|
69
|
+
attr_reader :cells
|
|
70
|
+
|
|
71
|
+
# Create a new ScreenBuffer with the given dimensions
|
|
72
|
+
#
|
|
73
|
+
# @param width [Integer] Screen width
|
|
74
|
+
# @param height [Integer] Screen height
|
|
75
|
+
# @param cursor_x [Integer] Cursor X position
|
|
76
|
+
# @param cursor_y [Integer] Cursor Y position
|
|
77
|
+
# @param timestamp [Float, nil] Capture timestamp (defaults to current time)
|
|
78
|
+
def initialize(width:, height:, cursor_x: 0, cursor_y: 0, timestamp: nil)
|
|
79
|
+
@width = width
|
|
80
|
+
@height = height
|
|
81
|
+
@cursor_x = cursor_x
|
|
82
|
+
@cursor_y = cursor_y
|
|
83
|
+
@timestamp = timestamp || Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
84
|
+
@cells = Array.new(height) { Array.new(width) { empty_cell } }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Capture the current screen buffer state from the Windows console
|
|
88
|
+
#
|
|
89
|
+
# @return [ScreenBuffer, nil] Captured buffer or nil if capture fails
|
|
90
|
+
# @raise [PlatformError] If not running on Windows
|
|
91
|
+
def self.capture
|
|
92
|
+
unless Gem.win_platform?
|
|
93
|
+
raise PlatformError, "Screen buffer capture requires Windows"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
unless defined?(Rich::Win32Console)
|
|
97
|
+
require_relative "../rich/win32_console"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
buffer_data = Rich::Win32Console.capture_screen_buffer
|
|
101
|
+
return nil unless buffer_data
|
|
102
|
+
|
|
103
|
+
from_win32_data(buffer_data)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Create a ScreenBuffer from Win32Console capture data
|
|
107
|
+
#
|
|
108
|
+
# @param data [Hash] Data from Win32Console.capture_screen_buffer
|
|
109
|
+
# @return [ScreenBuffer]
|
|
110
|
+
def self.from_win32_data(data)
|
|
111
|
+
buffer = new(
|
|
112
|
+
width: data[:width],
|
|
113
|
+
height: data[:height],
|
|
114
|
+
cursor_x: data[:cursor_x],
|
|
115
|
+
cursor_y: data[:cursor_y]
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
data[:lines].each_with_index do |line, row|
|
|
119
|
+
chars = line[:chars]
|
|
120
|
+
attributes = line[:attributes]
|
|
121
|
+
|
|
122
|
+
chars.each_char.with_index do |char, col|
|
|
123
|
+
break if col >= buffer.width
|
|
124
|
+
|
|
125
|
+
attr = attributes[col] || 0
|
|
126
|
+
fg, bg = parse_windows_attributes(attr)
|
|
127
|
+
|
|
128
|
+
buffer.set_cell(row, col, Cell.new(
|
|
129
|
+
char: char,
|
|
130
|
+
foreground: fg,
|
|
131
|
+
background: bg,
|
|
132
|
+
attributes: attr
|
|
133
|
+
))
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
buffer
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Parse Windows console attributes into foreground and background colors
|
|
141
|
+
#
|
|
142
|
+
# @param attributes [Integer] Windows console attribute value
|
|
143
|
+
# @return [Array<Integer, Integer>] [foreground, background] ANSI color numbers
|
|
144
|
+
def self.parse_windows_attributes(attributes)
|
|
145
|
+
# Extract foreground (bits 0-3)
|
|
146
|
+
fg = attributes & 0x0F
|
|
147
|
+
|
|
148
|
+
# Extract background (bits 4-7)
|
|
149
|
+
bg = (attributes >> 4) & 0x0F
|
|
150
|
+
|
|
151
|
+
[fg, bg]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Set a cell at the given position
|
|
155
|
+
#
|
|
156
|
+
# @param row [Integer] Row index (0-indexed)
|
|
157
|
+
# @param col [Integer] Column index (0-indexed)
|
|
158
|
+
# @param cell [Cell] Cell to set
|
|
159
|
+
# @return [void]
|
|
160
|
+
def set_cell(row, col, cell)
|
|
161
|
+
return if row < 0 || row >= @height || col < 0 || col >= @width
|
|
162
|
+
|
|
163
|
+
@cells[row][col] = cell
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Get a cell at the given position
|
|
167
|
+
#
|
|
168
|
+
# @param row [Integer] Row index (0-indexed)
|
|
169
|
+
# @param col [Integer] Column index (0-indexed)
|
|
170
|
+
# @return [Cell, nil] Cell at position or nil if out of bounds
|
|
171
|
+
def get_cell(row, col)
|
|
172
|
+
return nil if row < 0 || row >= @height || col < 0 || col >= @width
|
|
173
|
+
|
|
174
|
+
@cells[row][col]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Compare this buffer with another and generate ANSI diff
|
|
178
|
+
#
|
|
179
|
+
# Produces the minimal ANSI escape sequence needed to transform
|
|
180
|
+
# the other buffer's display into this buffer's display.
|
|
181
|
+
#
|
|
182
|
+
# @param other [ScreenBuffer, nil] Previous buffer state (nil = empty screen)
|
|
183
|
+
# @return [String] ANSI escape sequences to apply the changes
|
|
184
|
+
def diff(other = nil)
|
|
185
|
+
output = StringIO.new
|
|
186
|
+
|
|
187
|
+
# If no previous buffer, render everything
|
|
188
|
+
if other.nil?
|
|
189
|
+
output << to_ansi
|
|
190
|
+
return output.string
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Track what needs to be updated
|
|
194
|
+
changes = []
|
|
195
|
+
|
|
196
|
+
@height.times do |row|
|
|
197
|
+
@width.times do |col|
|
|
198
|
+
current = get_cell(row, col)
|
|
199
|
+
previous = other.get_cell(row, col)
|
|
200
|
+
|
|
201
|
+
# Only emit changes
|
|
202
|
+
if current != previous
|
|
203
|
+
changes << { row: row, col: col, cell: current }
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# If more than 50% changed, just redraw the whole screen
|
|
209
|
+
total_cells = @width * @height
|
|
210
|
+
if changes.length > total_cells / 2
|
|
211
|
+
output << "\e[H" # Move to home
|
|
212
|
+
output << to_ansi
|
|
213
|
+
return output.string
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Apply incremental changes
|
|
217
|
+
last_row = -1
|
|
218
|
+
last_col = -1
|
|
219
|
+
last_fg = nil
|
|
220
|
+
last_bg = nil
|
|
221
|
+
|
|
222
|
+
changes.each do |change|
|
|
223
|
+
row = change[:row]
|
|
224
|
+
col = change[:col]
|
|
225
|
+
cell = change[:cell]
|
|
226
|
+
|
|
227
|
+
# Move cursor if needed
|
|
228
|
+
if row != last_row || col != last_col + 1
|
|
229
|
+
output << "\e[#{row + 1};#{col + 1}H"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Set colors if changed
|
|
233
|
+
if cell.foreground != last_fg || cell.background != last_bg
|
|
234
|
+
output << ansi_color_code(cell.foreground, cell.background)
|
|
235
|
+
last_fg = cell.foreground
|
|
236
|
+
last_bg = cell.background
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
output << cell.char
|
|
240
|
+
|
|
241
|
+
last_row = row
|
|
242
|
+
last_col = col
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Handle cursor position change
|
|
246
|
+
if @cursor_x != other.cursor_x || @cursor_y != other.cursor_y
|
|
247
|
+
output << "\e[#{@cursor_y + 1};#{@cursor_x + 1}H"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
output.string
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Convert entire buffer to ANSI escape sequence string
|
|
254
|
+
#
|
|
255
|
+
# @return [String] Full ANSI representation of the screen buffer
|
|
256
|
+
def to_ansi
|
|
257
|
+
output = StringIO.new
|
|
258
|
+
last_fg = nil
|
|
259
|
+
last_bg = nil
|
|
260
|
+
|
|
261
|
+
@cells.each_with_index do |row_cells, row|
|
|
262
|
+
row_cells.each do |cell|
|
|
263
|
+
# Set colors if changed
|
|
264
|
+
if cell.foreground != last_fg || cell.background != last_bg
|
|
265
|
+
output << "\e[0m" if last_fg || last_bg # Reset first
|
|
266
|
+
output << ansi_color_code(cell.foreground, cell.background)
|
|
267
|
+
last_fg = cell.foreground
|
|
268
|
+
last_bg = cell.background
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
output << cell.char
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Newline between rows (except last)
|
|
275
|
+
if row < @height - 1
|
|
276
|
+
output << "\e[0m" if last_fg || last_bg # Reset before newline
|
|
277
|
+
output << "\r\n"
|
|
278
|
+
last_fg = nil
|
|
279
|
+
last_bg = nil
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Final reset
|
|
284
|
+
output << "\e[0m"
|
|
285
|
+
|
|
286
|
+
output.string
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Convert buffer to plain text (no color codes)
|
|
290
|
+
#
|
|
291
|
+
# @return [String] Plain text content of the buffer
|
|
292
|
+
def to_text
|
|
293
|
+
@cells.map { |row| row.map(&:char).join.rstrip }.join("\n").rstrip
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Check if this buffer equals another (content-wise)
|
|
297
|
+
#
|
|
298
|
+
# @param other [ScreenBuffer] Buffer to compare
|
|
299
|
+
# @return [Boolean] True if buffers have identical content
|
|
300
|
+
def ==(other)
|
|
301
|
+
return false unless other.is_a?(ScreenBuffer)
|
|
302
|
+
return false if @width != other.width || @height != other.height
|
|
303
|
+
|
|
304
|
+
@cells.each_with_index do |row_cells, row|
|
|
305
|
+
row_cells.each_with_index do |cell, col|
|
|
306
|
+
return false unless cell == other.get_cell(row, col)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
true
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
alias eql? ==
|
|
314
|
+
|
|
315
|
+
# @return [Integer] Hash code for this buffer
|
|
316
|
+
def hash
|
|
317
|
+
[@width, @height, @cells].hash
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Check if buffer content has changed from another
|
|
321
|
+
#
|
|
322
|
+
# @param other [ScreenBuffer] Buffer to compare
|
|
323
|
+
# @return [Boolean] True if any content differs
|
|
324
|
+
def changed?(other)
|
|
325
|
+
self != other
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
private
|
|
329
|
+
|
|
330
|
+
# Create an empty cell with default colors
|
|
331
|
+
#
|
|
332
|
+
# @return [Cell] Empty cell
|
|
333
|
+
def empty_cell
|
|
334
|
+
Cell.new(char: " ", foreground: 7, background: 0, attributes: 7)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Generate ANSI color code for foreground and background
|
|
338
|
+
#
|
|
339
|
+
# @param fg [Integer] Foreground color (0-15)
|
|
340
|
+
# @param bg [Integer] Background color (0-15)
|
|
341
|
+
# @return [String] ANSI escape sequence
|
|
342
|
+
def ansi_color_code(fg, bg)
|
|
343
|
+
codes = []
|
|
344
|
+
|
|
345
|
+
# Map Windows color to ANSI (0-7 standard, 8-15 bright)
|
|
346
|
+
if fg < 8
|
|
347
|
+
codes << (30 + WINDOWS_TO_ANSI_COLOR[fg])
|
|
348
|
+
else
|
|
349
|
+
codes << (90 + WINDOWS_TO_ANSI_COLOR[fg - 8])
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
if bg < 8
|
|
353
|
+
codes << (40 + WINDOWS_TO_ANSI_COLOR[bg])
|
|
354
|
+
else
|
|
355
|
+
codes << (100 + WINDOWS_TO_ANSI_COLOR[bg - 8])
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
"\e[#{codes.join(";")}m"
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Windows console colors use different bit ordering than ANSI
|
|
362
|
+
# Windows: BGR (Blue=1, Green=2, Red=4)
|
|
363
|
+
# ANSI: RGB (Red=1, Green=2, Blue=4)
|
|
364
|
+
WINDOWS_TO_ANSI_COLOR = [
|
|
365
|
+
0, # 0: Black -> 0
|
|
366
|
+
4, # 1: Blue -> 4
|
|
367
|
+
2, # 2: Green -> 2
|
|
368
|
+
6, # 3: Cyan -> 6
|
|
369
|
+
1, # 4: Red -> 1
|
|
370
|
+
5, # 5: Magenta -> 5
|
|
371
|
+
3, # 6: Yellow -> 3
|
|
372
|
+
7 # 7: White -> 7
|
|
373
|
+
].freeze
|
|
374
|
+
end
|
|
375
|
+
end
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AsciinemaWin
|
|
4
|
+
# Terminal color themes for SVG/HTML export
|
|
5
|
+
#
|
|
6
|
+
# Provides color palettes for rendering terminal output in various
|
|
7
|
+
# popular themes like Dracula, Monokai, Solarized, etc.
|
|
8
|
+
module Themes
|
|
9
|
+
# Base theme structure
|
|
10
|
+
# @!attribute [r] name
|
|
11
|
+
# @return [String] Theme name
|
|
12
|
+
# @!attribute [r] background
|
|
13
|
+
# @return [String] Background color (hex)
|
|
14
|
+
# @!attribute [r] foreground
|
|
15
|
+
# @return [String] Default foreground color (hex)
|
|
16
|
+
# @!attribute [r] cursor
|
|
17
|
+
# @return [String] Cursor color (hex)
|
|
18
|
+
# @!attribute [r] palette
|
|
19
|
+
# @return [Array<String>] 16-color palette (8 normal + 8 bright)
|
|
20
|
+
Theme = Data.define(:name, :background, :foreground, :cursor, :palette) do
|
|
21
|
+
# Get color for ANSI color index
|
|
22
|
+
#
|
|
23
|
+
# @param index [Integer] ANSI color index (0-15 for basic, 16-255 for extended)
|
|
24
|
+
# @return [String] Hex color code
|
|
25
|
+
def color(index)
|
|
26
|
+
if index < 16
|
|
27
|
+
palette[index]
|
|
28
|
+
elsif index < 232
|
|
29
|
+
# 216 color cube (6x6x6)
|
|
30
|
+
index -= 16
|
|
31
|
+
r = (index / 36) % 6
|
|
32
|
+
g = (index / 6) % 6
|
|
33
|
+
b = index % 6
|
|
34
|
+
r_val = r == 0 ? 0 : 55 + r * 40
|
|
35
|
+
g_val = g == 0 ? 0 : 55 + g * 40
|
|
36
|
+
b_val = b == 0 ? 0 : 55 + b * 40
|
|
37
|
+
format("#%02x%02x%02x", r_val, g_val, b_val)
|
|
38
|
+
else
|
|
39
|
+
# 24 grayscale colors
|
|
40
|
+
gray = (index - 232) * 10 + 8
|
|
41
|
+
format("#%02x%02x%02x", gray, gray, gray)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get foreground color for ANSI code
|
|
46
|
+
#
|
|
47
|
+
# @param code [Integer] ANSI SGR foreground code (30-37, 90-97)
|
|
48
|
+
# @return [String] Hex color code
|
|
49
|
+
def fg_color(code)
|
|
50
|
+
case code
|
|
51
|
+
when 30..37 then palette[code - 30]
|
|
52
|
+
when 90..97 then palette[code - 90 + 8]
|
|
53
|
+
when 39 then foreground
|
|
54
|
+
else foreground
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get background color for ANSI code
|
|
59
|
+
#
|
|
60
|
+
# @param code [Integer] ANSI SGR background code (40-47, 100-107)
|
|
61
|
+
# @return [String] Hex color code
|
|
62
|
+
def bg_color(code)
|
|
63
|
+
case code
|
|
64
|
+
when 40..47 then palette[code - 40]
|
|
65
|
+
when 100..107 then palette[code - 100 + 8]
|
|
66
|
+
when 49 then background
|
|
67
|
+
else background
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Default asciinema theme
|
|
73
|
+
ASCIINEMA = Theme.new(
|
|
74
|
+
name: "asciinema",
|
|
75
|
+
background: "#121314",
|
|
76
|
+
foreground: "#cccccc",
|
|
77
|
+
cursor: "#cccccc",
|
|
78
|
+
palette: [
|
|
79
|
+
"#000000", # Black
|
|
80
|
+
"#dd3c69", # Red
|
|
81
|
+
"#4ebf22", # Green
|
|
82
|
+
"#ddaf3c", # Yellow
|
|
83
|
+
"#26b0d7", # Blue
|
|
84
|
+
"#b954e1", # Magenta
|
|
85
|
+
"#54e1b9", # Cyan
|
|
86
|
+
"#d9d9d9", # White
|
|
87
|
+
"#4d4d4d", # Bright Black
|
|
88
|
+
"#dd3c69", # Bright Red
|
|
89
|
+
"#4ebf22", # Bright Green
|
|
90
|
+
"#ddaf3c", # Bright Yellow
|
|
91
|
+
"#26b0d7", # Bright Blue
|
|
92
|
+
"#b954e1", # Bright Magenta
|
|
93
|
+
"#54e1b9", # Bright Cyan
|
|
94
|
+
"#ffffff" # Bright White
|
|
95
|
+
]
|
|
96
|
+
).freeze
|
|
97
|
+
|
|
98
|
+
# Dracula theme
|
|
99
|
+
DRACULA = Theme.new(
|
|
100
|
+
name: "dracula",
|
|
101
|
+
background: "#282a36",
|
|
102
|
+
foreground: "#f8f8f2",
|
|
103
|
+
cursor: "#f8f8f2",
|
|
104
|
+
palette: [
|
|
105
|
+
"#21222c", # Black
|
|
106
|
+
"#ff5555", # Red
|
|
107
|
+
"#50fa7b", # Green
|
|
108
|
+
"#f1fa8c", # Yellow
|
|
109
|
+
"#bd93f9", # Blue
|
|
110
|
+
"#ff79c6", # Magenta
|
|
111
|
+
"#8be9fd", # Cyan
|
|
112
|
+
"#f8f8f2", # White
|
|
113
|
+
"#6272a4", # Bright Black
|
|
114
|
+
"#ff6e6e", # Bright Red
|
|
115
|
+
"#69ff94", # Bright Green
|
|
116
|
+
"#ffffa5", # Bright Yellow
|
|
117
|
+
"#d6acff", # Bright Blue
|
|
118
|
+
"#ff92df", # Bright Magenta
|
|
119
|
+
"#a4ffff", # Bright Cyan
|
|
120
|
+
"#ffffff" # Bright White
|
|
121
|
+
]
|
|
122
|
+
).freeze
|
|
123
|
+
|
|
124
|
+
# Monokai theme
|
|
125
|
+
MONOKAI = Theme.new(
|
|
126
|
+
name: "monokai",
|
|
127
|
+
background: "#272822",
|
|
128
|
+
foreground: "#f8f8f2",
|
|
129
|
+
cursor: "#f8f8f2",
|
|
130
|
+
palette: [
|
|
131
|
+
"#272822", # Black
|
|
132
|
+
"#f92672", # Red
|
|
133
|
+
"#a6e22e", # Green
|
|
134
|
+
"#f4bf75", # Yellow
|
|
135
|
+
"#66d9ef", # Blue
|
|
136
|
+
"#ae81ff", # Magenta
|
|
137
|
+
"#a1efe4", # Cyan
|
|
138
|
+
"#f8f8f2", # White
|
|
139
|
+
"#75715e", # Bright Black
|
|
140
|
+
"#f92672", # Bright Red
|
|
141
|
+
"#a6e22e", # Bright Green
|
|
142
|
+
"#f4bf75", # Bright Yellow
|
|
143
|
+
"#66d9ef", # Bright Blue
|
|
144
|
+
"#ae81ff", # Bright Magenta
|
|
145
|
+
"#a1efe4", # Bright Cyan
|
|
146
|
+
"#f9f8f5" # Bright White
|
|
147
|
+
]
|
|
148
|
+
).freeze
|
|
149
|
+
|
|
150
|
+
# Solarized Dark theme
|
|
151
|
+
SOLARIZED_DARK = Theme.new(
|
|
152
|
+
name: "solarized-dark",
|
|
153
|
+
background: "#002b36",
|
|
154
|
+
foreground: "#839496",
|
|
155
|
+
cursor: "#839496",
|
|
156
|
+
palette: [
|
|
157
|
+
"#073642", # Black
|
|
158
|
+
"#dc322f", # Red
|
|
159
|
+
"#859900", # Green
|
|
160
|
+
"#b58900", # Yellow
|
|
161
|
+
"#268bd2", # Blue
|
|
162
|
+
"#d33682", # Magenta
|
|
163
|
+
"#2aa198", # Cyan
|
|
164
|
+
"#eee8d5", # White
|
|
165
|
+
"#002b36", # Bright Black
|
|
166
|
+
"#cb4b16", # Bright Red
|
|
167
|
+
"#586e75", # Bright Green
|
|
168
|
+
"#657b83", # Bright Yellow
|
|
169
|
+
"#839496", # Bright Blue
|
|
170
|
+
"#6c71c4", # Bright Magenta
|
|
171
|
+
"#93a1a1", # Bright Cyan
|
|
172
|
+
"#fdf6e3" # Bright White
|
|
173
|
+
]
|
|
174
|
+
).freeze
|
|
175
|
+
|
|
176
|
+
# Solarized Light theme
|
|
177
|
+
SOLARIZED_LIGHT = Theme.new(
|
|
178
|
+
name: "solarized-light",
|
|
179
|
+
background: "#fdf6e3",
|
|
180
|
+
foreground: "#657b83",
|
|
181
|
+
cursor: "#657b83",
|
|
182
|
+
palette: [
|
|
183
|
+
"#073642", # Black
|
|
184
|
+
"#dc322f", # Red
|
|
185
|
+
"#859900", # Green
|
|
186
|
+
"#b58900", # Yellow
|
|
187
|
+
"#268bd2", # Blue
|
|
188
|
+
"#d33682", # Magenta
|
|
189
|
+
"#2aa198", # Cyan
|
|
190
|
+
"#eee8d5", # White
|
|
191
|
+
"#002b36", # Bright Black
|
|
192
|
+
"#cb4b16", # Bright Red
|
|
193
|
+
"#586e75", # Bright Green
|
|
194
|
+
"#657b83", # Bright Yellow
|
|
195
|
+
"#839496", # Bright Blue
|
|
196
|
+
"#6c71c4", # Bright Magenta
|
|
197
|
+
"#93a1a1", # Bright Cyan
|
|
198
|
+
"#fdf6e3" # Bright White
|
|
199
|
+
]
|
|
200
|
+
).freeze
|
|
201
|
+
|
|
202
|
+
# Nord theme
|
|
203
|
+
NORD = Theme.new(
|
|
204
|
+
name: "nord",
|
|
205
|
+
background: "#2e3440",
|
|
206
|
+
foreground: "#d8dee9",
|
|
207
|
+
cursor: "#d8dee9",
|
|
208
|
+
palette: [
|
|
209
|
+
"#3b4252", # Black
|
|
210
|
+
"#bf616a", # Red
|
|
211
|
+
"#a3be8c", # Green
|
|
212
|
+
"#ebcb8b", # Yellow
|
|
213
|
+
"#81a1c1", # Blue
|
|
214
|
+
"#b48ead", # Magenta
|
|
215
|
+
"#88c0d0", # Cyan
|
|
216
|
+
"#e5e9f0", # White
|
|
217
|
+
"#4c566a", # Bright Black
|
|
218
|
+
"#bf616a", # Bright Red
|
|
219
|
+
"#a3be8c", # Bright Green
|
|
220
|
+
"#ebcb8b", # Bright Yellow
|
|
221
|
+
"#81a1c1", # Bright Blue
|
|
222
|
+
"#b48ead", # Bright Magenta
|
|
223
|
+
"#8fbcbb", # Bright Cyan
|
|
224
|
+
"#eceff4" # Bright White
|
|
225
|
+
]
|
|
226
|
+
).freeze
|
|
227
|
+
|
|
228
|
+
# One Dark theme (Atom)
|
|
229
|
+
ONE_DARK = Theme.new(
|
|
230
|
+
name: "one-dark",
|
|
231
|
+
background: "#282c34",
|
|
232
|
+
foreground: "#abb2bf",
|
|
233
|
+
cursor: "#528bff",
|
|
234
|
+
palette: [
|
|
235
|
+
"#282c34", # Black
|
|
236
|
+
"#e06c75", # Red
|
|
237
|
+
"#98c379", # Green
|
|
238
|
+
"#e5c07b", # Yellow
|
|
239
|
+
"#61afef", # Blue
|
|
240
|
+
"#c678dd", # Magenta
|
|
241
|
+
"#56b6c2", # Cyan
|
|
242
|
+
"#abb2bf", # White
|
|
243
|
+
"#545862", # Bright Black
|
|
244
|
+
"#e06c75", # Bright Red
|
|
245
|
+
"#98c379", # Bright Green
|
|
246
|
+
"#e5c07b", # Bright Yellow
|
|
247
|
+
"#61afef", # Bright Blue
|
|
248
|
+
"#c678dd", # Bright Magenta
|
|
249
|
+
"#56b6c2", # Bright Cyan
|
|
250
|
+
"#c8ccd4" # Bright White
|
|
251
|
+
]
|
|
252
|
+
).freeze
|
|
253
|
+
|
|
254
|
+
# GitHub Dark theme
|
|
255
|
+
GITHUB_DARK = Theme.new(
|
|
256
|
+
name: "github-dark",
|
|
257
|
+
background: "#0d1117",
|
|
258
|
+
foreground: "#c9d1d9",
|
|
259
|
+
cursor: "#c9d1d9",
|
|
260
|
+
palette: [
|
|
261
|
+
"#484f58", # Black
|
|
262
|
+
"#ff7b72", # Red
|
|
263
|
+
"#3fb950", # Green
|
|
264
|
+
"#d29922", # Yellow
|
|
265
|
+
"#58a6ff", # Blue
|
|
266
|
+
"#bc8cff", # Magenta
|
|
267
|
+
"#39c5cf", # Cyan
|
|
268
|
+
"#b1bac4", # White
|
|
269
|
+
"#6e7681", # Bright Black
|
|
270
|
+
"#ffa198", # Bright Red
|
|
271
|
+
"#56d364", # Bright Green
|
|
272
|
+
"#e3b341", # Bright Yellow
|
|
273
|
+
"#79c0ff", # Bright Blue
|
|
274
|
+
"#d2a8ff", # Bright Magenta
|
|
275
|
+
"#56d4dd", # Bright Cyan
|
|
276
|
+
"#f0f6fc" # Bright White
|
|
277
|
+
]
|
|
278
|
+
).freeze
|
|
279
|
+
|
|
280
|
+
# Tokyo Night theme
|
|
281
|
+
TOKYO_NIGHT = Theme.new(
|
|
282
|
+
name: "tokyo-night",
|
|
283
|
+
background: "#1a1b26",
|
|
284
|
+
foreground: "#a9b1d6",
|
|
285
|
+
cursor: "#c0caf5",
|
|
286
|
+
palette: [
|
|
287
|
+
"#15161e", # Black
|
|
288
|
+
"#f7768e", # Red
|
|
289
|
+
"#9ece6a", # Green
|
|
290
|
+
"#e0af68", # Yellow
|
|
291
|
+
"#7aa2f7", # Blue
|
|
292
|
+
"#bb9af7", # Magenta
|
|
293
|
+
"#7dcfff", # Cyan
|
|
294
|
+
"#a9b1d6", # White
|
|
295
|
+
"#414868", # Bright Black
|
|
296
|
+
"#f7768e", # Bright Red
|
|
297
|
+
"#9ece6a", # Bright Green
|
|
298
|
+
"#e0af68", # Bright Yellow
|
|
299
|
+
"#7aa2f7", # Bright Blue
|
|
300
|
+
"#bb9af7", # Bright Magenta
|
|
301
|
+
"#7dcfff", # Bright Cyan
|
|
302
|
+
"#c0caf5" # Bright White
|
|
303
|
+
]
|
|
304
|
+
).freeze
|
|
305
|
+
|
|
306
|
+
# All available themes
|
|
307
|
+
ALL = {
|
|
308
|
+
"asciinema" => ASCIINEMA,
|
|
309
|
+
"dracula" => DRACULA,
|
|
310
|
+
"monokai" => MONOKAI,
|
|
311
|
+
"solarized-dark" => SOLARIZED_DARK,
|
|
312
|
+
"solarized-light" => SOLARIZED_LIGHT,
|
|
313
|
+
"nord" => NORD,
|
|
314
|
+
"one-dark" => ONE_DARK,
|
|
315
|
+
"github-dark" => GITHUB_DARK,
|
|
316
|
+
"tokyo-night" => TOKYO_NIGHT
|
|
317
|
+
}.freeze
|
|
318
|
+
|
|
319
|
+
# Get theme by name
|
|
320
|
+
#
|
|
321
|
+
# @param name [String] Theme name
|
|
322
|
+
# @return [Theme] Theme or default if not found
|
|
323
|
+
def self.get(name)
|
|
324
|
+
ALL[name.to_s.downcase] || ASCIINEMA
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# List available theme names
|
|
328
|
+
#
|
|
329
|
+
# @return [Array<String>] Theme names
|
|
330
|
+
def self.names
|
|
331
|
+
ALL.keys
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|