Class: Tuile::StyledString::Style
- Inherits:
-
Object
- Object
- Tuile::StyledString::Style
- Defined in:
- lib/tuile/styled_string.rb
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.
new
Instance Attribute Summary collapse
- #bg ⇒ Color? readonly
- #bold ⇒ Boolean readonly
- #fg ⇒ Color? readonly
- #italic ⇒ Boolean readonly
- #strikethrough ⇒ Boolean readonly
- #underline ⇒ Boolean readonly
Class Method Summary collapse
Instance Method Summary collapse
- #default? ⇒ Boolean
-
#merge(**overrides) ⇒ Style
Returns a new Style with the given attributes overridden.
-
#sgr_to(other) ⇒ String
Minimal SGR escape that transitions a terminal already showing ‘self` into `other`: only the attributes that differ are emitted.
Instance Attribute Details
#bg ⇒ Color? (readonly)
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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/tuile/styled_string.rb', line 83 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. # # Shared by {StyledString#to_ansi} (diffing span-to-span from the default # style) and {Buffer}'s flush (diffing cell-to-cell against the style the # terminal currently holds), so both emit identical minimal sequences. # @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 |
#bold ⇒ Boolean (readonly)
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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/tuile/styled_string.rb', line 83 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. # # Shared by {StyledString#to_ansi} (diffing span-to-span from the default # style) and {Buffer}'s flush (diffing cell-to-cell against the style the # terminal currently holds), so both emit identical minimal sequences. # @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 |
#fg ⇒ Color? (readonly)
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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/tuile/styled_string.rb', line 83 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. # # Shared by {StyledString#to_ansi} (diffing span-to-span from the default # style) and {Buffer}'s flush (diffing cell-to-cell against the style the # terminal currently holds), so both emit identical minimal sequences. # @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 |
#italic ⇒ Boolean (readonly)
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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/tuile/styled_string.rb', line 83 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. # # Shared by {StyledString#to_ansi} (diffing span-to-span from the default # style) and {Buffer}'s flush (diffing cell-to-cell against the style the # terminal currently holds), so both emit identical minimal sequences. # @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 |
#strikethrough ⇒ Boolean (readonly)
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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/tuile/styled_string.rb', line 83 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. # # Shared by {StyledString#to_ansi} (diffing span-to-span from the default # style) and {Buffer}'s flush (diffing cell-to-cell against the style the # terminal currently holds), so both emit identical minimal sequences. # @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 |
#underline ⇒ Boolean (readonly)
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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/tuile/styled_string.rb', line 83 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. # # Shared by {StyledString#to_ansi} (diffing span-to-span from the default # style) and {Buffer}'s flush (diffing cell-to-cell against the style the # terminal currently holds), so both emit identical minimal sequences. # @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) ⇒ Style
92 93 94 |
# File 'lib/tuile/styled_string.rb', line 92 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
#default? ⇒ Boolean
102 |
# File 'lib/tuile/styled_string.rb', line 102 def default? = self == DEFAULT |
#merge(**overrides) ⇒ Style
Returns a new Tuile::StyledString::Style with the given attributes overridden.
107 |
# File 'lib/tuile/styled_string.rb', line 107 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.
Shared by Tuile::StyledString#to_ansi (diffing span-to-span from the default style) and Buffer‘s flush (diffing cell-to-cell against the style the terminal currently holds), so both emit identical minimal sequences.
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/tuile/styled_string.rb', line 120 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 |