Class: Tuile::StyledString::Style

Inherits:
Object
  • Object
show all
Defined in:
lib/tuile/styled_string.rb,
sig/tuile.rbs

Overview

A frozen value type describing the visual style of a Span. Colors are stored as Color instances (or nil for the terminal default); inputs to Style.new and #merge are coerced via Color.coerce, so the four accepted color forms — nil, Symbol, Integer 0..255, RGB Array — work transparently.

Constant Summary collapse

DEFAULT =

The style with no color and no attributes — what the terminal shows without any SGR applied.

Returns:

new

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#bgColor? (readonly)

Returns:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tuile/styled_string.rb', line 65

class Style < Data.define(:fg, :bg, :bold, :italic, :underline, :strikethrough)
  # @param fg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bold [Boolean]
  # @param italic [Boolean]
  # @param underline [Boolean]
  # @param strikethrough [Boolean]
  # @return [Style]
  # @raise [ArgumentError] when a color is not one of the accepted forms.
  def self.new(fg: nil, bg: nil, bold: false, italic: false, underline: false, strikethrough: false)
    super(fg: Color.coerce(fg), bg: Color.coerce(bg), bold:, italic:, underline:, strikethrough:)
  end

  # The style with no color and no attributes — what the terminal shows
  # without any SGR applied.
  # @return [Style]
  DEFAULT = new

  # @return [Boolean]
  def default? = self == DEFAULT

  # Returns a new {Style} with the given attributes overridden.
  # @param overrides [Hash{Symbol => Object}]
  # @return [Style]
  def merge(**overrides) = self.class.new(**to_h.merge(overrides))

  # Minimal SGR escape that transitions a terminal already showing `self`
  # into `other`: only the attributes that differ are emitted. Returns
  # `""` when the styles are identical (nothing to do), and {Ansi::RESET}
  # (`\e[0m`, one code) when `other` is the default style — shorter than
  # turning each attribute off individually.
  # @param other [Style] the style to transition to.
  # @return [String]
  def sgr_to(other)
    return "" if self == other
    return Ansi::RESET if other.default?

    codes = []
    codes << (other.bold ? 1 : 22) if bold != other.bold
    codes << (other.italic ? 3 : 23) if italic != other.italic
    codes << (other.underline ? 4 : 24) if underline != other.underline
    codes << (other.strikethrough ? 9 : 29) if strikethrough != other.strikethrough
    codes.concat(color_codes(other.fg, target: :fg)) if fg != other.fg
    codes.concat(color_codes(other.bg, target: :bg)) if bg != other.bg
    return "" if codes.empty?

    "\e[#{codes.join(";")}m"
  end

  private

  # @param color [Color, nil]
  # @param target [Symbol] either `:fg` or `:bg`.
  # @return [Array<Integer>] SGR codes; `[39]` / `[49]` for the "default"
  #   reset when `color` is `nil`, otherwise delegated to {Color#sgr_codes}.
  def color_codes(color, target:)
    return [target == :fg ? 39 : 49] if color.nil?

    color.sgr_codes(target)
  end
end

#boldBoolean (readonly)

Returns:

  • (Boolean)


65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tuile/styled_string.rb', line 65

class Style < Data.define(:fg, :bg, :bold, :italic, :underline, :strikethrough)
  # @param fg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bold [Boolean]
  # @param italic [Boolean]
  # @param underline [Boolean]
  # @param strikethrough [Boolean]
  # @return [Style]
  # @raise [ArgumentError] when a color is not one of the accepted forms.
  def self.new(fg: nil, bg: nil, bold: false, italic: false, underline: false, strikethrough: false)
    super(fg: Color.coerce(fg), bg: Color.coerce(bg), bold:, italic:, underline:, strikethrough:)
  end

  # The style with no color and no attributes — what the terminal shows
  # without any SGR applied.
  # @return [Style]
  DEFAULT = new

  # @return [Boolean]
  def default? = self == DEFAULT

  # Returns a new {Style} with the given attributes overridden.
  # @param overrides [Hash{Symbol => Object}]
  # @return [Style]
  def merge(**overrides) = self.class.new(**to_h.merge(overrides))

  # Minimal SGR escape that transitions a terminal already showing `self`
  # into `other`: only the attributes that differ are emitted. Returns
  # `""` when the styles are identical (nothing to do), and {Ansi::RESET}
  # (`\e[0m`, one code) when `other` is the default style — shorter than
  # turning each attribute off individually.
  # @param other [Style] the style to transition to.
  # @return [String]
  def sgr_to(other)
    return "" if self == other
    return Ansi::RESET if other.default?

    codes = []
    codes << (other.bold ? 1 : 22) if bold != other.bold
    codes << (other.italic ? 3 : 23) if italic != other.italic
    codes << (other.underline ? 4 : 24) if underline != other.underline
    codes << (other.strikethrough ? 9 : 29) if strikethrough != other.strikethrough
    codes.concat(color_codes(other.fg, target: :fg)) if fg != other.fg
    codes.concat(color_codes(other.bg, target: :bg)) if bg != other.bg
    return "" if codes.empty?

    "\e[#{codes.join(";")}m"
  end

  private

  # @param color [Color, nil]
  # @param target [Symbol] either `:fg` or `:bg`.
  # @return [Array<Integer>] SGR codes; `[39]` / `[49]` for the "default"
  #   reset when `color` is `nil`, otherwise delegated to {Color#sgr_codes}.
  def color_codes(color, target:)
    return [target == :fg ? 39 : 49] if color.nil?

    color.sgr_codes(target)
  end
end

#fgColor? (readonly)

Returns:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tuile/styled_string.rb', line 65

class Style < Data.define(:fg, :bg, :bold, :italic, :underline, :strikethrough)
  # @param fg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bold [Boolean]
  # @param italic [Boolean]
  # @param underline [Boolean]
  # @param strikethrough [Boolean]
  # @return [Style]
  # @raise [ArgumentError] when a color is not one of the accepted forms.
  def self.new(fg: nil, bg: nil, bold: false, italic: false, underline: false, strikethrough: false)
    super(fg: Color.coerce(fg), bg: Color.coerce(bg), bold:, italic:, underline:, strikethrough:)
  end

  # The style with no color and no attributes — what the terminal shows
  # without any SGR applied.
  # @return [Style]
  DEFAULT = new

  # @return [Boolean]
  def default? = self == DEFAULT

  # Returns a new {Style} with the given attributes overridden.
  # @param overrides [Hash{Symbol => Object}]
  # @return [Style]
  def merge(**overrides) = self.class.new(**to_h.merge(overrides))

  # Minimal SGR escape that transitions a terminal already showing `self`
  # into `other`: only the attributes that differ are emitted. Returns
  # `""` when the styles are identical (nothing to do), and {Ansi::RESET}
  # (`\e[0m`, one code) when `other` is the default style — shorter than
  # turning each attribute off individually.
  # @param other [Style] the style to transition to.
  # @return [String]
  def sgr_to(other)
    return "" if self == other
    return Ansi::RESET if other.default?

    codes = []
    codes << (other.bold ? 1 : 22) if bold != other.bold
    codes << (other.italic ? 3 : 23) if italic != other.italic
    codes << (other.underline ? 4 : 24) if underline != other.underline
    codes << (other.strikethrough ? 9 : 29) if strikethrough != other.strikethrough
    codes.concat(color_codes(other.fg, target: :fg)) if fg != other.fg
    codes.concat(color_codes(other.bg, target: :bg)) if bg != other.bg
    return "" if codes.empty?

    "\e[#{codes.join(";")}m"
  end

  private

  # @param color [Color, nil]
  # @param target [Symbol] either `:fg` or `:bg`.
  # @return [Array<Integer>] SGR codes; `[39]` / `[49]` for the "default"
  #   reset when `color` is `nil`, otherwise delegated to {Color#sgr_codes}.
  def color_codes(color, target:)
    return [target == :fg ? 39 : 49] if color.nil?

    color.sgr_codes(target)
  end
end

#italicBoolean (readonly)

Returns:

  • (Boolean)


65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tuile/styled_string.rb', line 65

class Style < Data.define(:fg, :bg, :bold, :italic, :underline, :strikethrough)
  # @param fg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bold [Boolean]
  # @param italic [Boolean]
  # @param underline [Boolean]
  # @param strikethrough [Boolean]
  # @return [Style]
  # @raise [ArgumentError] when a color is not one of the accepted forms.
  def self.new(fg: nil, bg: nil, bold: false, italic: false, underline: false, strikethrough: false)
    super(fg: Color.coerce(fg), bg: Color.coerce(bg), bold:, italic:, underline:, strikethrough:)
  end

  # The style with no color and no attributes — what the terminal shows
  # without any SGR applied.
  # @return [Style]
  DEFAULT = new

  # @return [Boolean]
  def default? = self == DEFAULT

  # Returns a new {Style} with the given attributes overridden.
  # @param overrides [Hash{Symbol => Object}]
  # @return [Style]
  def merge(**overrides) = self.class.new(**to_h.merge(overrides))

  # Minimal SGR escape that transitions a terminal already showing `self`
  # into `other`: only the attributes that differ are emitted. Returns
  # `""` when the styles are identical (nothing to do), and {Ansi::RESET}
  # (`\e[0m`, one code) when `other` is the default style — shorter than
  # turning each attribute off individually.
  # @param other [Style] the style to transition to.
  # @return [String]
  def sgr_to(other)
    return "" if self == other
    return Ansi::RESET if other.default?

    codes = []
    codes << (other.bold ? 1 : 22) if bold != other.bold
    codes << (other.italic ? 3 : 23) if italic != other.italic
    codes << (other.underline ? 4 : 24) if underline != other.underline
    codes << (other.strikethrough ? 9 : 29) if strikethrough != other.strikethrough
    codes.concat(color_codes(other.fg, target: :fg)) if fg != other.fg
    codes.concat(color_codes(other.bg, target: :bg)) if bg != other.bg
    return "" if codes.empty?

    "\e[#{codes.join(";")}m"
  end

  private

  # @param color [Color, nil]
  # @param target [Symbol] either `:fg` or `:bg`.
  # @return [Array<Integer>] SGR codes; `[39]` / `[49]` for the "default"
  #   reset when `color` is `nil`, otherwise delegated to {Color#sgr_codes}.
  def color_codes(color, target:)
    return [target == :fg ? 39 : 49] if color.nil?

    color.sgr_codes(target)
  end
end

#strikethroughBoolean (readonly)

Returns:

  • (Boolean)


65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tuile/styled_string.rb', line 65

class Style < Data.define(:fg, :bg, :bold, :italic, :underline, :strikethrough)
  # @param fg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bold [Boolean]
  # @param italic [Boolean]
  # @param underline [Boolean]
  # @param strikethrough [Boolean]
  # @return [Style]
  # @raise [ArgumentError] when a color is not one of the accepted forms.
  def self.new(fg: nil, bg: nil, bold: false, italic: false, underline: false, strikethrough: false)
    super(fg: Color.coerce(fg), bg: Color.coerce(bg), bold:, italic:, underline:, strikethrough:)
  end

  # The style with no color and no attributes — what the terminal shows
  # without any SGR applied.
  # @return [Style]
  DEFAULT = new

  # @return [Boolean]
  def default? = self == DEFAULT

  # Returns a new {Style} with the given attributes overridden.
  # @param overrides [Hash{Symbol => Object}]
  # @return [Style]
  def merge(**overrides) = self.class.new(**to_h.merge(overrides))

  # Minimal SGR escape that transitions a terminal already showing `self`
  # into `other`: only the attributes that differ are emitted. Returns
  # `""` when the styles are identical (nothing to do), and {Ansi::RESET}
  # (`\e[0m`, one code) when `other` is the default style — shorter than
  # turning each attribute off individually.
  # @param other [Style] the style to transition to.
  # @return [String]
  def sgr_to(other)
    return "" if self == other
    return Ansi::RESET if other.default?

    codes = []
    codes << (other.bold ? 1 : 22) if bold != other.bold
    codes << (other.italic ? 3 : 23) if italic != other.italic
    codes << (other.underline ? 4 : 24) if underline != other.underline
    codes << (other.strikethrough ? 9 : 29) if strikethrough != other.strikethrough
    codes.concat(color_codes(other.fg, target: :fg)) if fg != other.fg
    codes.concat(color_codes(other.bg, target: :bg)) if bg != other.bg
    return "" if codes.empty?

    "\e[#{codes.join(";")}m"
  end

  private

  # @param color [Color, nil]
  # @param target [Symbol] either `:fg` or `:bg`.
  # @return [Array<Integer>] SGR codes; `[39]` / `[49]` for the "default"
  #   reset when `color` is `nil`, otherwise delegated to {Color#sgr_codes}.
  def color_codes(color, target:)
    return [target == :fg ? 39 : 49] if color.nil?

    color.sgr_codes(target)
  end
end

#underlineBoolean (readonly)

Returns:

  • (Boolean)


65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tuile/styled_string.rb', line 65

class Style < Data.define(:fg, :bg, :bold, :italic, :underline, :strikethrough)
  # @param fg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bg [Color, Symbol, Integer, Array<Integer>, nil] coerced via {Color.coerce}.
  # @param bold [Boolean]
  # @param italic [Boolean]
  # @param underline [Boolean]
  # @param strikethrough [Boolean]
  # @return [Style]
  # @raise [ArgumentError] when a color is not one of the accepted forms.
  def self.new(fg: nil, bg: nil, bold: false, italic: false, underline: false, strikethrough: false)
    super(fg: Color.coerce(fg), bg: Color.coerce(bg), bold:, italic:, underline:, strikethrough:)
  end

  # The style with no color and no attributes — what the terminal shows
  # without any SGR applied.
  # @return [Style]
  DEFAULT = new

  # @return [Boolean]
  def default? = self == DEFAULT

  # Returns a new {Style} with the given attributes overridden.
  # @param overrides [Hash{Symbol => Object}]
  # @return [Style]
  def merge(**overrides) = self.class.new(**to_h.merge(overrides))

  # Minimal SGR escape that transitions a terminal already showing `self`
  # into `other`: only the attributes that differ are emitted. Returns
  # `""` when the styles are identical (nothing to do), and {Ansi::RESET}
  # (`\e[0m`, one code) when `other` is the default style — shorter than
  # turning each attribute off individually.
  # @param other [Style] the style to transition to.
  # @return [String]
  def sgr_to(other)
    return "" if self == other
    return Ansi::RESET if other.default?

    codes = []
    codes << (other.bold ? 1 : 22) if bold != other.bold
    codes << (other.italic ? 3 : 23) if italic != other.italic
    codes << (other.underline ? 4 : 24) if underline != other.underline
    codes << (other.strikethrough ? 9 : 29) if strikethrough != other.strikethrough
    codes.concat(color_codes(other.fg, target: :fg)) if fg != other.fg
    codes.concat(color_codes(other.bg, target: :bg)) if bg != other.bg
    return "" if codes.empty?

    "\e[#{codes.join(";")}m"
  end

  private

  # @param color [Color, nil]
  # @param target [Symbol] either `:fg` or `:bg`.
  # @return [Array<Integer>] SGR codes; `[39]` / `[49]` for the "default"
  #   reset when `color` is `nil`, otherwise delegated to {Color#sgr_codes}.
  def color_codes(color, target:)
    return [target == :fg ? 39 : 49] if color.nil?

    color.sgr_codes(target)
  end
end

Class Method Details

.new(fg: nil, bg: nil, bold: false, italic: false, underline: false, strikethrough: false) ⇒ Object

@param fg — coerced via Color.coerce.

@param bg — coerced via Color.coerce.

@param bold

@param italic

@param underline

@param strikethrough



74
75
76
# File 'lib/tuile/styled_string.rb', line 74

def self.new(fg: nil, bg: nil, bold: false, italic: false, underline: false, strikethrough: false)
  super(fg: Color.coerce(fg), bg: Color.coerce(bg), bold:, italic:, underline:, strikethrough:)
end

Instance Method Details

#color_codes(color, target:) ⇒ ::Array[Integer]

@param color

@param target — either :fg or :bg.

@return — SGR codes; [39] / [49] for the "default" reset when color is nil, otherwise delegated to Color#sgr_codes.

Parameters:

  • color (Color, nil)
  • target: (Symbol)

Returns:

  • (::Array[Integer])


120
121
122
123
124
# File 'lib/tuile/styled_string.rb', line 120

def color_codes(color, target:)
  return [target == :fg ? 39 : 49] if color.nil?

  color.sgr_codes(target)
end

#default?Boolean

Returns:

  • (Boolean)


84
# File 'lib/tuile/styled_string.rb', line 84

def default? = self == DEFAULT

#merge(**overrides) ⇒ Style

Returns a new Tuile::StyledString::Style with the given attributes overridden.

@param overrides

Parameters:

  • overrides (::Hash[Symbol, Object])

Returns:



89
# File 'lib/tuile/styled_string.rb', line 89

def merge(**overrides) = self.class.new(**to_h.merge(overrides))

#sgr_to(other) ⇒ String

Minimal SGR escape that transitions a terminal already showing self into other: only the attributes that differ are emitted. Returns "" when the styles are identical (nothing to do), and Ansi::RESET (\e[0m, one code) when other is the default style — shorter than turning each attribute off individually.

@param other — the style to transition to.

Parameters:

Returns:

  • (String)


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/tuile/styled_string.rb', line 98

def sgr_to(other)
  return "" if self == other
  return Ansi::RESET if other.default?

  codes = []
  codes << (other.bold ? 1 : 22) if bold != other.bold
  codes << (other.italic ? 3 : 23) if italic != other.italic
  codes << (other.underline ? 4 : 24) if underline != other.underline
  codes << (other.strikethrough ? 9 : 29) if strikethrough != other.strikethrough
  codes.concat(color_codes(other.fg, target: :fg)) if fg != other.fg
  codes.concat(color_codes(other.bg, target: :bg)) if bg != other.bg
  return "" if codes.empty?

  "\e[#{codes.join(";")}m"
end