Class: Rich::Style

Inherits:
Object
  • Object
show all
Defined in:
lib/rich/style.rb

Overview

Represents a terminal style with colors and text attributes. Styles are immutable and can be combined using the + operator.

Constant Summary collapse

ANSI_CODES =

ANSI reset and attribute codes

{
  bold: "1",
  dim: "2",
  italic: "3",
  underline: "4",
  blink: "5",
  blink2: "6",
  reverse: "7",
  conceal: "8",
  strike: "9",
  underline2: "21",
  frame: "51",
  encircle: "52",
  overline: "53"
}.freeze
ATTRIBUTE_ALIASES =

Single-letter attribute aliases (expanded before parsing). Mirrors the shorthands documented in the cheat-sheet, e.g. [u] == [underline].

{
  "b" => "bold",
  "i" => "italic",
  "u" => "underline",
  "s" => "strike",
  "d" => "dim",
  "r" => "reverse",
  "o" => "overline"
}.freeze
STYLE_REGEX =

Regex for parsing style definitions

/
  (?<not>not\s+)?
  (?<attr>bold|dim|italic|underline2?|blink2?|reverse|conceal|strike|frame|encircle|overline)|
  (?<link>link\s+(?<url>\S+))|
  (?<on>on\s+)?(?<color>\S+)
/x

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(color: nil, bgcolor: nil, bold: nil, dim: nil, italic: nil, underline: nil, blink: nil, blink2: nil, reverse: nil, conceal: nil, strike: nil, underline2: nil, frame: nil, encircle: nil, overline: nil, link: nil, meta: nil) ⇒ Style

Create a new style

Parameters:

  • color (Color, String, nil) (defaults to: nil)

    Foreground color

  • bgcolor (Color, String, nil) (defaults to: nil)

    Background color

  • bold (Boolean, nil) (defaults to: nil)

    Bold attribute

  • dim (Boolean, nil) (defaults to: nil)

    Dim attribute

  • italic (Boolean, nil) (defaults to: nil)

    Italic attribute

  • underline (Boolean, nil) (defaults to: nil)

    Underline attribute

  • blink (Boolean, nil) (defaults to: nil)

    Blink attribute

  • blink2 (Boolean, nil) (defaults to: nil)

    Rapid blink attribute

  • reverse (Boolean, nil) (defaults to: nil)

    Reverse video attribute

  • conceal (Boolean, nil) (defaults to: nil)

    Conceal attribute

  • strike (Boolean, nil) (defaults to: nil)

    Strikethrough attribute

  • underline2 (Boolean, nil) (defaults to: nil)

    Double underline attribute

  • frame (Boolean, nil) (defaults to: nil)

    Frame attribute

  • encircle (Boolean, nil) (defaults to: nil)

    Encircle attribute

  • overline (Boolean, nil) (defaults to: nil)

    Overline attribute

  • link (String, nil) (defaults to: nil)

    Hyperlink URL

  • meta (Hash, nil) (defaults to: nil)

    Meta information



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/rich/style.rb', line 121

def initialize(
  color: nil,
  bgcolor: nil,
  bold: nil,
  dim: nil,
  italic: nil,
  underline: nil,
  blink: nil,
  blink2: nil,
  reverse: nil,
  conceal: nil,
  strike: nil,
  underline2: nil,
  frame: nil,
  encircle: nil,
  overline: nil,
  link: nil,
  meta: nil
)
  @color = parse_color(color)
  @bgcolor = parse_color(bgcolor)
  @link = link&.freeze
  @meta = meta&.freeze

  # Build attribute masks
  @set_attributes = 0
  @attributes = 0

  attrs = {
    bold: bold, dim: dim, italic: italic, underline: underline,
    blink: blink, blink2: blink2, reverse: reverse, conceal: conceal,
    strike: strike, underline2: underline2, frame: frame,
    encircle: encircle, overline: overline
  }

  attrs.each do |name, value|
    next if value.nil?

    bit = StyleAttribute::ALL[name]
    @set_attributes |= bit
    @attributes |= bit if value
  end

  # Per-color-system render cache. freeze is shallow, so this Hash stays
  # mutable even though the Style is frozen (Style is immutable, so the
  # rendered escape for a given color system never changes).
  @render_cache = {}

  freeze
end

Instance Attribute Details

#attributesInteger (readonly)

Returns Attribute values (0 = off, 1 = on).

Returns:

  • (Integer)

    Attribute values (0 = off, 1 = on)



91
92
93
# File 'lib/rich/style.rb', line 91

def attributes
  @attributes
end

#bgcolorColor? (readonly)

Returns Background color.

Returns:

  • (Color, nil)

    Background color



85
86
87
# File 'lib/rich/style.rb', line 85

def bgcolor
  @bgcolor
end

#colorColor? (readonly)

Returns Foreground color.

Returns:

  • (Color, nil)

    Foreground color



82
83
84
# File 'lib/rich/style.rb', line 82

def color
  @color
end

Returns Hyperlink URL.

Returns:

  • (String, nil)

    Hyperlink URL



94
95
96
# File 'lib/rich/style.rb', line 94

def link
  @link
end

#metaHash? (readonly)

Returns Meta information.

Returns:

  • (Hash, nil)

    Meta information



97
98
99
# File 'lib/rich/style.rb', line 97

def meta
  @meta
end

#set_attributesInteger (readonly)

Returns Attributes that are explicitly set.

Returns:

  • (Integer)

    Attributes that are explicitly set



88
89
90
# File 'lib/rich/style.rb', line 88

def set_attributes
  @set_attributes
end

Class Method Details

.combine(color:, bgcolor:, link:, meta:, set_attributes:, attributes:) ⇒ Style

Create a combined style with explicit attributes (internal use)

Parameters:

  • color (Color, nil)

    Foreground color

  • bgcolor (Color, nil)

    Background color

  • link (String, nil)

    Hyperlink

  • meta (Hash, nil)

    Meta info

  • set_attributes (Integer)

    Set attributes bitmask

  • attributes (Integer)

    Attribute values bitmask

Returns:



346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/rich/style.rb', line 346

def combine(color:, bgcolor:, link:, meta:, set_attributes:, attributes:)
  style = allocate
  style.instance_variable_set(:@color, color)
  style.instance_variable_set(:@bgcolor, bgcolor)
  style.instance_variable_set(:@link, link&.freeze)
  style.instance_variable_set(:@meta, meta&.freeze)
  style.instance_variable_set(:@set_attributes, set_attributes)
  style.instance_variable_set(:@attributes, attributes)
  style.instance_variable_set(:@render_cache, {})
  style.freeze
  style
end

.from_color(color: nil, bgcolor: nil) ⇒ Style

Create a style from just colors

Parameters:

  • color (Color, String, nil) (defaults to: nil)

    Foreground color

  • bgcolor (Color, String, nil) (defaults to: nil)

    Background color

Returns:



363
364
365
# File 'lib/rich/style.rb', line 363

def from_color(color: nil, bgcolor: nil)
  new(color: color, bgcolor: bgcolor)
end

.from_meta(meta) ⇒ Style

Create a style with meta information

Parameters:

  • meta (Hash)

    Meta data

Returns:



370
371
372
# File 'lib/rich/style.rb', line 370

def from_meta(meta)
  new(meta: meta)
end

.normalize(style) ⇒ String

Normalize a style definition

Parameters:

  • style (String)

    Style definition

Returns:

  • (String)

    Normalized style definition



377
378
379
# File 'lib/rich/style.rb', line 377

def normalize(style)
  parse(style).to_s
end

.nullStyle

Create a null (empty) style

Returns:



334
335
336
# File 'lib/rich/style.rb', line 334

def null
  @null ||= new
end

.parse(style) ⇒ Style

Parse a style definition string

Parameters:

  • style (String, Style, nil)

    Style definition

Returns:



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/rich/style.rb', line 313

def parse(style)
  return null if style.nil? || (style.is_a?(String) && style.empty?)
  return style if style.is_a?(Style)

  style = style.to_s

  @parse_cache_mutex.synchronize do
    return @parse_cache[style] if @parse_cache.key?(style)
  end

  result = parse_uncached(style)

  @parse_cache_mutex.synchronize do
    @parse_cache[style] = result
  end

  result
end

Instance Method Details

#+(other) ⇒ Style

Combine two styles (right-hand style takes precedence)

Parameters:

  • other (Style)

    Style to combine with

Returns:

  • (Style)

    Combined style



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/rich/style.rb', line 251

def +(other)
  return self if other.nil? || other.blank?
  return other if blank?

  new_color = other.color || @color
  new_bgcolor = other.bgcolor || @bgcolor
  new_link = other.link || @link
  new_meta = @meta || other.meta ? (@meta || {}).merge(other.meta || {}) : nil

  # Merge attributes
  new_set = @set_attributes | other.set_attributes
  new_attrs = (@attributes & ~other.set_attributes) | (other.attributes & other.set_attributes)

  Style.combine(
    color: new_color,
    bgcolor: new_bgcolor,
    link: new_link,
    meta: new_meta,
    set_attributes: new_set,
    attributes: new_attrs
  )
end

#==(other) ⇒ Object Also known as: eql?



293
294
295
296
297
298
299
300
301
# File 'lib/rich/style.rb', line 293

def ==(other)
  return false unless other.is_a?(Style)

  @color == other.color &&
    @bgcolor == other.bgcolor &&
    @set_attributes == other.set_attributes &&
    @attributes == other.attributes &&
    @link == other.link
end

#[](name) ⇒ Boolean?

Get a specific attribute value

Parameters:

  • name (Symbol)

    Attribute name

Returns:

  • (Boolean, nil)

    Attribute value or nil if not set



187
188
189
190
191
192
193
194
# File 'lib/rich/style.rb', line 187

def [](name)
  bit = StyleAttribute::ALL[name]
  return nil unless bit

  return nil if (@set_attributes & bit) == 0

  (@attributes & bit) != 0
end

#background_styleStyle

Get background-only style

Returns:



289
290
291
# File 'lib/rich/style.rb', line 289

def background_style
  Style.new(bgcolor: @bgcolor)
end

#blank?Boolean

Check if any attributes, colors, link, or meta are set

Returns:

  • (Boolean)


174
175
176
177
# File 'lib/rich/style.rb', line 174

def blank?
  @color.nil? && @bgcolor.nil? && @set_attributes == 0 && @link.nil? &&
    (@meta.nil? || @meta.empty?)
end

#hashObject



305
306
307
# File 'lib/rich/style.rb', line 305

def hash
  [@color, @bgcolor, @set_attributes, @attributes, @link].hash
end

#inspectObject



233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/rich/style.rb', line 233

def inspect
  attrs = []
  attrs << "color=#{@color.name}" if @color
  attrs << "bgcolor=#{@bgcolor.name}" if @bgcolor

  StyleAttribute::NAMES.each do |name|
    value = self[name]
    attrs << "#{name}=#{value}" unless value.nil?
  end

  attrs << "link=#{@link.inspect}" if @link

  "#<Rich::Style #{attrs.join(' ')}>"
end

#present?Boolean

Returns False if blank.

Returns:

  • (Boolean)

    False if blank



180
181
182
# File 'lib/rich/style.rb', line 180

def present?
  !blank?
end

#render(color_system: ColorSystem::TRUECOLOR) ⇒ String

Generate ANSI escape codes for this style

Parameters:

  • color_system (Symbol) (defaults to: ColorSystem::TRUECOLOR)

    Target color system

Returns:

  • (String)

    ANSI escape sequence



205
206
207
208
209
210
211
212
# File 'lib/rich/style.rb', line 205

def render(color_system: ColorSystem::TRUECOLOR)
  cache = @render_cache
  return cache[color_system] if cache&.key?(color_system)

  result = compute_render(color_system)
  cache[color_system] = result if cache
  result
end

#to_sString

Generate the style definition string

Returns:

  • (String)


216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/rich/style.rb', line 216

def to_s
  parts = []

  StyleAttribute::NAMES.each do |name|
    value = self[name]
    next if value.nil?

    parts << (value ? name.to_s : "not #{name}")
  end

  parts << @color.name if @color
  parts << "on #{@bgcolor.name}" if @bgcolor
  parts << "link #{@link}" if @link

  parts.join(" ")
end

#without_colorStyle

Get style with no colors

Returns:



276
277
278
279
280
281
282
283
284
285
# File 'lib/rich/style.rb', line 276

def without_color
  Style.combine(
    color: nil,
    bgcolor: nil,
    link: @link,
    meta: @meta,
    set_attributes: @set_attributes,
    attributes: @attributes
  )
end